mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-04 00:04:03 -04:00
[FEATURE] Add new snapping option for "Line Endpoints"
When enabled, this snapping mode snaps to the beginning or end vertex of lines only. When snapping to a polygon layer, only the first vertex in rings will be snapped to. Refs Natural resources Canada Contract: 3000720707
This commit is contained in:
parent
6d40826766
commit
b8baabf3f4
@ -401,7 +401,7 @@
|
||||
<file>themes/default/mActionSaveEdits.svg</file>
|
||||
<file>themes/default/mActionSaveMapAsImage.svg</file>
|
||||
<file>themes/default/mActionScaleBar.svg</file>
|
||||
<file>themes/default/mActionScaleFeature.svg</file>
|
||||
<file>themes/default/mActionScaleFeature.svg</file>
|
||||
<file>themes/default/mActionScriptOpen.svg</file>
|
||||
<file>themes/default/mActionSelect.svg</file>
|
||||
<file>themes/default/mActionSelectAll.svg</file>
|
||||
@ -907,6 +907,7 @@
|
||||
<file>themes/default/transformation.svg</file>
|
||||
<file>themes/default/mIconCodeEditor.svg</file>
|
||||
<file>themes/default/console/iconSyntaxErrorConsoleParams.svg</file>
|
||||
<file>themes/default/mIconSnappingEndpoint.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/images/tips">
|
||||
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
|
||||
|
1
images/themes/default/mIconSnappingEndpoint.svg
Normal file
1
images/themes/default/mIconSnappingEndpoint.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M 5.5,5.5 12,18 18,8" fill="none" stroke="#8cbe8c" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/><path d="m 4.5,4.5 h 3 v 3 h -3 z" fill="#bebebe" stroke="#8c8c8c"/><path d="m 16.5,6.5 h 3 v 3 h -3 z" fill="#bebebe" stroke="#8c8c8c"/></svg>
|
After Width: | Height: | Size: 351 B |
@ -94,6 +94,7 @@ Configure render context - if not ``None``, it will use to index only visible f
|
||||
Area,
|
||||
Centroid,
|
||||
MiddleOfSegment,
|
||||
LineEndpoint,
|
||||
All
|
||||
};
|
||||
|
||||
@ -134,23 +135,30 @@ construct invalid match
|
||||
bool isValid() const;
|
||||
bool hasVertex() const;
|
||||
%Docstring
|
||||
Returns true if the Match is a vertex
|
||||
Returns ``True`` if the Match is a vertex
|
||||
%End
|
||||
bool hasEdge() const;
|
||||
%Docstring
|
||||
Returns true if the Match is an edge
|
||||
Returns ``True`` if the Match is an edge
|
||||
%End
|
||||
bool hasCentroid() const;
|
||||
%Docstring
|
||||
Returns true if the Match is a centroid
|
||||
Returns ``True`` if the Match is a centroid
|
||||
%End
|
||||
bool hasArea() const;
|
||||
%Docstring
|
||||
Returns true if the Match is an area
|
||||
Returns ``True`` if the Match is an area
|
||||
%End
|
||||
bool hasMiddleSegment() const;
|
||||
%Docstring
|
||||
Returns true if the Match is the middle of a segment
|
||||
Returns ``True`` if the Match is the middle of a segment
|
||||
%End
|
||||
|
||||
bool hasLineEndpoint() const;
|
||||
%Docstring
|
||||
Returns ``True`` if the Match is a line endpoint (start or end vertex).
|
||||
|
||||
.. versionadded:: 3.20
|
||||
%End
|
||||
|
||||
double distance() const;
|
||||
@ -231,6 +239,15 @@ Optional filter may discard unwanted matches.
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
|
||||
.. versionadded:: 3.12
|
||||
%End
|
||||
|
||||
Match nearestLineEndpoints( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
%Docstring
|
||||
Find nearest line endpoint (start or end vertex) to the specified point - up to distance specified by tolerance
|
||||
Optional filter may discard unwanted matches.
|
||||
This method is either blocking or non blocking according to ``relaxed`` parameter passed
|
||||
|
||||
.. versionadded:: 3.20
|
||||
%End
|
||||
|
||||
Match nearestEdge( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = 0, bool relaxed = false );
|
||||
|
@ -47,6 +47,7 @@ This is a container for configuration of the snapping of the project
|
||||
AreaFlag,
|
||||
CentroidFlag,
|
||||
MiddleOfSegmentFlag,
|
||||
LineEndpointFlag,
|
||||
};
|
||||
typedef QFlags<QgsSnappingConfig::SnappingTypes> SnappingTypeFlag;
|
||||
|
||||
@ -58,14 +59,21 @@ This is a container for configuration of the snapping of the project
|
||||
PerLayer
|
||||
};
|
||||
|
||||
static const QString snappingTypeFlagToString( SnappingTypeFlag type );
|
||||
static QString snappingTypeFlagToString( SnappingTypeFlag type );
|
||||
%Docstring
|
||||
Convenient method to returns the translated name of the enum type
|
||||
QgsSnappingConfig.SnappingTypeFlag
|
||||
QgsSnappingConfig.SnappingTypeFlag.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
%End
|
||||
|
||||
static QIcon snappingTypeFlagToIcon( SnappingTypeFlag type );
|
||||
%Docstring
|
||||
Convenient method to return an icon corresponding to the enum type
|
||||
QgsSnappingConfig.SnappingTypeFlag.
|
||||
|
||||
.. versionadded:: 3.20
|
||||
%End
|
||||
|
||||
class IndividualLayerSettings
|
||||
{
|
||||
|
@ -1075,12 +1075,23 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
|
||||
|
||||
//default snap mode
|
||||
mSnappingEnabledDefault->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/default_snap_enabled" ), false ).toBool() );
|
||||
mDefaultSnapModeComboBox->addItem( tr( "No Snapping" ), QgsSnappingConfig::NoSnapFlag );
|
||||
mDefaultSnapModeComboBox->addItem( tr( "Vertex" ), QgsSnappingConfig::VertexFlag );
|
||||
mDefaultSnapModeComboBox->addItem( tr( "Segment" ), QgsSnappingConfig::SegmentFlag );
|
||||
mDefaultSnapModeComboBox->addItem( tr( "Centroid" ), QgsSnappingConfig::CentroidFlag );
|
||||
mDefaultSnapModeComboBox->addItem( tr( "Middle of Segments" ), QgsSnappingConfig::MiddleOfSegmentFlag );
|
||||
mDefaultSnapModeComboBox->addItem( tr( "Area" ), QgsSnappingConfig::AreaFlag );
|
||||
|
||||
for ( QgsSnappingConfig::SnappingTypes type :
|
||||
{
|
||||
QgsSnappingConfig::NoSnapFlag,
|
||||
QgsSnappingConfig::VertexFlag,
|
||||
QgsSnappingConfig::SegmentFlag,
|
||||
QgsSnappingConfig::CentroidFlag,
|
||||
QgsSnappingConfig::MiddleOfSegmentFlag,
|
||||
QgsSnappingConfig::LineEndpointFlag,
|
||||
QgsSnappingConfig::AreaFlag,
|
||||
} )
|
||||
{
|
||||
mDefaultSnapModeComboBox->addItem( QgsSnappingConfig::snappingTypeFlagToIcon( type ),
|
||||
QgsSnappingConfig::snappingTypeFlagToString( type ),
|
||||
type );
|
||||
}
|
||||
|
||||
QgsSnappingConfig::SnappingTypeFlag defaultSnapMode = mSettings->flagValue( QStringLiteral( "/qgis/digitizing/default_snap_type" ), QgsSnappingConfig::VertexFlag );
|
||||
mDefaultSnapModeComboBox->setCurrentIndex( mDefaultSnapModeComboBox->findData( static_cast<int>( defaultSnapMode ) ) );
|
||||
mDefaultSnappingToleranceSpinBox->setValue( mSettings->value( QStringLiteral( "/qgis/digitizing/default_snapping_tolerance" ), Qgis::DEFAULT_SNAP_TOLERANCE ).toDouble() );
|
||||
|
@ -71,21 +71,22 @@ QWidget *QgsSnappingLayerDelegate::createEditor( QWidget *parent, const QStyleOp
|
||||
mTypeButton->setToolTip( tr( "Snapping Type" ) );
|
||||
mTypeButton->setPopupMode( QToolButton::InstantPopup );
|
||||
SnapTypeMenu *typeMenu = new SnapTypeMenu( tr( "Set Snapping Mode" ), parent );
|
||||
QAction *mVertexAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingVertex.svg" ) ), tr( "Vertex" ), typeMenu );
|
||||
QAction *mSegmentAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingSegment.svg" ) ), tr( "Segment" ), typeMenu );
|
||||
QAction *mAreaAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingArea.svg" ) ), tr( "Area" ), typeMenu );
|
||||
QAction *mCentroidAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingCentroid.svg" ) ), tr( "Centroid" ), typeMenu );
|
||||
QAction *mMiddleAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingMiddle.svg" ) ), tr( "Middle of Segments" ), typeMenu );
|
||||
mVertexAction->setCheckable( true );
|
||||
mSegmentAction->setCheckable( true );
|
||||
mAreaAction->setCheckable( true );
|
||||
mCentroidAction->setCheckable( true );
|
||||
mMiddleAction->setCheckable( true );
|
||||
typeMenu->addAction( mVertexAction );
|
||||
typeMenu->addAction( mSegmentAction );
|
||||
typeMenu->addAction( mAreaAction );
|
||||
typeMenu->addAction( mCentroidAction );
|
||||
typeMenu->addAction( mMiddleAction );
|
||||
|
||||
for ( QgsSnappingConfig::SnappingTypes type :
|
||||
{
|
||||
QgsSnappingConfig::VertexFlag,
|
||||
QgsSnappingConfig::SegmentFlag,
|
||||
QgsSnappingConfig::AreaFlag,
|
||||
QgsSnappingConfig::CentroidFlag,
|
||||
QgsSnappingConfig::MiddleOfSegmentFlag,
|
||||
QgsSnappingConfig::LineEndpointFlag
|
||||
} )
|
||||
{
|
||||
QAction *action = new QAction( QgsSnappingConfig::snappingTypeFlagToIcon( type ), QgsSnappingConfig::snappingTypeFlagToString( type ), typeMenu );
|
||||
action->setData( type );
|
||||
action->setCheckable( true );
|
||||
typeMenu->addAction( action );
|
||||
}
|
||||
mTypeButton->setMenu( typeMenu );
|
||||
mTypeButton->setObjectName( QStringLiteral( "SnappingTypeButton" ) );
|
||||
mTypeButton->setToolButtonStyle( Qt::ToolButtonTextBesideIcon );
|
||||
@ -161,13 +162,11 @@ void QgsSnappingLayerDelegate::setEditorData( QWidget *editor, const QModelIndex
|
||||
QToolButton *tb = qobject_cast<QToolButton *>( editor );
|
||||
if ( tb )
|
||||
{
|
||||
QList<QAction *>actions = tb->menu()->actions();
|
||||
|
||||
actions.at( 0 )->setChecked( type & QgsSnappingConfig::VertexFlag );
|
||||
actions.at( 1 )->setChecked( type & QgsSnappingConfig::SegmentFlag );
|
||||
actions.at( 2 )->setChecked( type & QgsSnappingConfig::AreaFlag );
|
||||
actions.at( 3 )->setChecked( type & QgsSnappingConfig::CentroidFlag );
|
||||
actions.at( 4 )->setChecked( type & QgsSnappingConfig::MiddleOfSegmentFlag );
|
||||
const QList<QAction *> actions = tb->menu()->actions();
|
||||
for ( QAction *action : actions )
|
||||
{
|
||||
action->setChecked( type & static_cast< QgsSnappingConfig::SnappingTypeFlag >( action->data().toInt() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( index.column() == QgsSnappingLayerTreeModel::ToleranceColumn )
|
||||
@ -212,18 +211,17 @@ void QgsSnappingLayerDelegate::setModelData( QWidget *editor, QAbstractItemModel
|
||||
QToolButton *t = qobject_cast<QToolButton *>( editor );
|
||||
if ( t )
|
||||
{
|
||||
QList<QAction *> actions = t->menu()->actions();
|
||||
const QList<QAction *> actions = t->menu()->actions();
|
||||
QgsSnappingConfig::SnappingTypeFlag type = QgsSnappingConfig::NoSnapFlag;
|
||||
if ( actions.at( 0 )->isChecked() )
|
||||
type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( type | QgsSnappingConfig::VertexFlag );
|
||||
if ( actions.at( 1 )->isChecked() )
|
||||
type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( type | QgsSnappingConfig::SegmentFlag );
|
||||
if ( actions.at( 2 )->isChecked() )
|
||||
type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( type | QgsSnappingConfig::AreaFlag );
|
||||
if ( actions.at( 3 )->isChecked() )
|
||||
type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( type | QgsSnappingConfig::CentroidFlag );
|
||||
if ( actions.at( 4 )->isChecked() )
|
||||
type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( type | QgsSnappingConfig::MiddleOfSegmentFlag );
|
||||
|
||||
for ( QAction *action : actions )
|
||||
{
|
||||
if ( action->isChecked() )
|
||||
{
|
||||
const QgsSnappingConfig::SnappingTypeFlag actionFlag = static_cast<QgsSnappingConfig::SnappingTypeFlag>( action->data().toInt() );
|
||||
type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( type | actionFlag );
|
||||
}
|
||||
}
|
||||
model->setData( index, static_cast<int>( type ), Qt::EditRole );
|
||||
}
|
||||
|
||||
|
@ -188,21 +188,24 @@ QgsSnappingWidget::QgsSnappingWidget( QgsProject *project, QgsMapCanvas *canvas,
|
||||
mTypeButton->setToolTip( tr( "Snapping Type" ) );
|
||||
mTypeButton->setPopupMode( QToolButton::InstantPopup );
|
||||
SnapTypeMenu *typeMenu = new SnapTypeMenu( tr( "Set Snapping Mode" ), this );
|
||||
mVertexAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingVertex.svg" ) ), tr( "Vertex" ), typeMenu );
|
||||
mSegmentAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingSegment.svg" ) ), tr( "Segment" ), typeMenu );
|
||||
mAreaAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingArea.svg" ) ), tr( "Area" ), typeMenu );
|
||||
mCentroidAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingCentroid.svg" ) ), tr( "Centroid" ), typeMenu );
|
||||
mMiddleAction = new QAction( QIcon( QgsApplication::getThemeIcon( "/mIconSnappingMiddle.svg" ) ), tr( "Middle of Segments" ), typeMenu );
|
||||
mVertexAction->setCheckable( true );
|
||||
mSegmentAction->setCheckable( true );
|
||||
mAreaAction->setCheckable( true );
|
||||
mCentroidAction->setCheckable( true );
|
||||
mMiddleAction->setCheckable( true );
|
||||
typeMenu->addAction( mVertexAction );
|
||||
typeMenu->addAction( mSegmentAction );
|
||||
typeMenu->addAction( mAreaAction );
|
||||
typeMenu->addAction( mCentroidAction );
|
||||
typeMenu->addAction( mMiddleAction );
|
||||
|
||||
for ( QgsSnappingConfig::SnappingTypes type :
|
||||
{
|
||||
QgsSnappingConfig::VertexFlag,
|
||||
QgsSnappingConfig::SegmentFlag,
|
||||
QgsSnappingConfig::AreaFlag,
|
||||
QgsSnappingConfig::CentroidFlag,
|
||||
QgsSnappingConfig::MiddleOfSegmentFlag,
|
||||
QgsSnappingConfig::LineEndpointFlag
|
||||
} )
|
||||
{
|
||||
QAction *action = new QAction( QgsSnappingConfig::snappingTypeFlagToIcon( type ), QgsSnappingConfig::snappingTypeFlagToString( type ), typeMenu );
|
||||
action->setData( type );
|
||||
action->setCheckable( true );
|
||||
typeMenu->addAction( action );
|
||||
mSnappingFlagActions << action;
|
||||
}
|
||||
|
||||
mTypeButton->setMenu( typeMenu );
|
||||
mTypeButton->setObjectName( QStringLiteral( "SnappingTypeButton" ) );
|
||||
if ( mDisplayMode == Widget )
|
||||
@ -456,37 +459,13 @@ void QgsSnappingWidget::projectSnapSettingsChanged()
|
||||
updateToleranceDecimals();
|
||||
}
|
||||
|
||||
// Clear
|
||||
mVertexAction->setChecked( false );
|
||||
mSegmentAction->setChecked( false );
|
||||
mAreaAction->setChecked( false );
|
||||
mCentroidAction->setChecked( false );
|
||||
mMiddleAction->setChecked( false );
|
||||
|
||||
if ( config.typeFlag() & QgsSnappingConfig::VertexFlag )
|
||||
// update snapping flag actions
|
||||
for ( QAction *action : qgis::as_const( mSnappingFlagActions ) )
|
||||
{
|
||||
mTypeButton->setDefaultAction( mVertexAction );
|
||||
mVertexAction->setChecked( true );
|
||||
}
|
||||
if ( config.typeFlag() & QgsSnappingConfig::SegmentFlag )
|
||||
{
|
||||
mTypeButton->setDefaultAction( mSegmentAction );
|
||||
mSegmentAction->setChecked( true );
|
||||
}
|
||||
if ( config.typeFlag() & QgsSnappingConfig::AreaFlag )
|
||||
{
|
||||
mTypeButton->setDefaultAction( mAreaAction );
|
||||
mAreaAction->setChecked( true );
|
||||
}
|
||||
if ( config.typeFlag() & QgsSnappingConfig::CentroidFlag )
|
||||
{
|
||||
mTypeButton->setDefaultAction( mCentroidAction );
|
||||
mCentroidAction->setChecked( true );
|
||||
}
|
||||
if ( config.typeFlag() & QgsSnappingConfig::MiddleOfSegmentFlag )
|
||||
{
|
||||
mTypeButton->setDefaultAction( mMiddleAction );
|
||||
mMiddleAction->setChecked( true );
|
||||
const QgsSnappingConfig::SnappingTypeFlag actionFlag = static_cast<QgsSnappingConfig::SnappingTypeFlag>( action->data().toInt() );
|
||||
action->setChecked( config.typeFlag() & actionFlag );
|
||||
if ( action->isChecked() )
|
||||
mTypeButton->setDefaultAction( action );
|
||||
}
|
||||
|
||||
if ( static_cast<QgsTolerance::UnitType>( mUnitsComboBox->currentData().toInt() ) != config.units() )
|
||||
@ -722,27 +701,27 @@ void QgsSnappingWidget::typeButtonTriggered( QAction *action )
|
||||
{
|
||||
unsigned int type = static_cast<int>( mConfig.typeFlag() );
|
||||
|
||||
mTypeButton->setDefaultAction( action );
|
||||
if ( action == mVertexAction )
|
||||
const QgsSnappingConfig::SnappingTypeFlag actionFlag = static_cast<QgsSnappingConfig::SnappingTypeFlag>( action->data().toInt() );
|
||||
type ^= actionFlag;
|
||||
|
||||
if ( type & actionFlag )
|
||||
{
|
||||
type ^= static_cast<int>( QgsSnappingConfig::VertexFlag );
|
||||
// user checked the action, set as new default
|
||||
mTypeButton->setDefaultAction( action );
|
||||
}
|
||||
else if ( action == mSegmentAction )
|
||||
else
|
||||
{
|
||||
type ^= static_cast<int>( QgsSnappingConfig::SegmentFlag );
|
||||
}
|
||||
else if ( action == mAreaAction )
|
||||
{
|
||||
type ^= static_cast<int>( QgsSnappingConfig::AreaFlag );
|
||||
}
|
||||
else if ( action == mCentroidAction )
|
||||
{
|
||||
type ^= static_cast<int>( QgsSnappingConfig::CentroidFlag );
|
||||
}
|
||||
else if ( action == mMiddleAction )
|
||||
{
|
||||
type ^= static_cast<int>( QgsSnappingConfig::MiddleOfSegmentFlag );
|
||||
// user unchecked the action -- find out which ones we should set as new default action
|
||||
for ( QAction *flagAction : qgis::as_const( mSnappingFlagActions ) )
|
||||
{
|
||||
if ( type & static_cast<QgsSnappingConfig::SnappingTypeFlag>( flagAction->data().toInt() ) )
|
||||
{
|
||||
mTypeButton->setDefaultAction( flagAction );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mConfig.setTypeFlag( static_cast<QgsSnappingConfig::SnappingTypeFlag>( type ) );
|
||||
mProject->setSnappingConfig( mConfig );
|
||||
}
|
||||
|
@ -157,11 +157,7 @@ class APP_EXPORT QgsSnappingWidget : public QWidget
|
||||
QAction *mEditAdvancedConfigAction = nullptr;
|
||||
QToolButton *mTypeButton = nullptr;
|
||||
QAction *mTypeAction = nullptr; // hide widget does not work on toolbar, action needed
|
||||
QAction *mVertexAction = nullptr;
|
||||
QAction *mSegmentAction = nullptr;
|
||||
QAction *mAreaAction = nullptr;
|
||||
QAction *mCentroidAction = nullptr;
|
||||
QAction *mMiddleAction = nullptr;
|
||||
QList< QAction * > mSnappingFlagActions;
|
||||
QDoubleSpinBox *mToleranceSpinBox = nullptr;
|
||||
QgsScaleWidget *mMinScaleWidget = nullptr;
|
||||
QgsScaleWidget *mMaxScaleWidget = nullptr;
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "qgsvectorlayerfeatureiterator.h"
|
||||
#include "qgsexpressioncontextutils.h"
|
||||
#include "qgslinestring.h"
|
||||
#include "qgscurvepolygon.h"
|
||||
#include "qgspointlocatorinittask.h"
|
||||
#include <spatialindex/SpatialIndex.h>
|
||||
|
||||
@ -158,8 +159,8 @@ class QgsPointLocator_VisitorNearestCentroid : public IVisitor
|
||||
, mFilter( filter )
|
||||
{}
|
||||
|
||||
void visitNode( const INode &n ) override { Q_UNUSED( n ); }
|
||||
void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
|
||||
void visitNode( const INode &n ) override { Q_UNUSED( n ) }
|
||||
void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
|
||||
|
||||
void visitData( const IData &d ) override
|
||||
{
|
||||
@ -210,8 +211,8 @@ class QgsPointLocator_VisitorNearestMiddleOfSegment: public IVisitor
|
||||
, mFilter( filter )
|
||||
{}
|
||||
|
||||
void visitNode( const INode &n ) override { Q_UNUSED( n ); }
|
||||
void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ); }
|
||||
void visitNode( const INode &n ) override { Q_UNUSED( n ) }
|
||||
void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
|
||||
|
||||
void visitData( const IData &d ) override
|
||||
{
|
||||
@ -245,6 +246,111 @@ class QgsPointLocator_VisitorNearestMiddleOfSegment: public IVisitor
|
||||
QgsPointLocator::MatchFilter *mFilter = nullptr;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief Helper class used when traversing the index looking for line endpoints (start or end vertex) - builds a list of matches.
|
||||
* \note not available in Python bindings
|
||||
* \since QGIS 3.20
|
||||
*/
|
||||
class QgsPointLocator_VisitorNearestLineEndpoint : public IVisitor
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \brief Helper class used when traversing the index looking for line endpoints (start or end vertex) - builds a list of matches.
|
||||
*/
|
||||
QgsPointLocator_VisitorNearestLineEndpoint( QgsPointLocator *pl, QgsPointLocator::Match &m, const QgsPointXY &srcPoint, QgsPointLocator::MatchFilter *filter = nullptr )
|
||||
: mLocator( pl )
|
||||
, mBest( m )
|
||||
, mSrcPoint( srcPoint )
|
||||
, mFilter( filter )
|
||||
{}
|
||||
|
||||
void visitNode( const INode &n ) override { Q_UNUSED( n ) }
|
||||
void visitData( std::vector<const IData *> &v ) override { Q_UNUSED( v ) }
|
||||
|
||||
void visitData( const IData &d ) override
|
||||
{
|
||||
QgsFeatureId id = d.getIdentifier();
|
||||
const QgsGeometry *geom = mLocator->mGeoms.value( id );
|
||||
|
||||
QgsPointXY bestPoint;
|
||||
int bestVertexNumber = -1;
|
||||
auto replaceIfBetter = [this, &bestPoint, &bestVertexNumber]( const QgsPoint & candidate, int vertexNumber )
|
||||
{
|
||||
if ( bestPoint.isEmpty() || candidate.distanceSquared( mSrcPoint.x(), mSrcPoint.y() ) < bestPoint.sqrDist( mSrcPoint ) )
|
||||
{
|
||||
bestPoint = QgsPointXY( candidate );
|
||||
bestVertexNumber = vertexNumber;
|
||||
}
|
||||
};
|
||||
|
||||
switch ( QgsWkbTypes::geometryType( geom->wkbType() ) )
|
||||
{
|
||||
case QgsWkbTypes::PointGeometry:
|
||||
case QgsWkbTypes::UnknownGeometry:
|
||||
case QgsWkbTypes::NullGeometry:
|
||||
return;
|
||||
|
||||
case QgsWkbTypes::LineGeometry:
|
||||
{
|
||||
int partStartVertexNum = 0;
|
||||
for ( auto partIt = geom->const_parts_begin(); partIt != geom->const_parts_end(); ++partIt )
|
||||
{
|
||||
if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( *partIt ) )
|
||||
{
|
||||
replaceIfBetter( curve->startPoint(), partStartVertexNum );
|
||||
replaceIfBetter( curve->endPoint(), partStartVertexNum + curve->numPoints() - 1 );
|
||||
partStartVertexNum += curve->numPoints();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case QgsWkbTypes::PolygonGeometry:
|
||||
{
|
||||
int partStartVertexNum = 0;
|
||||
for ( auto partIt = geom->const_parts_begin(); partIt != geom->const_parts_end(); ++partIt )
|
||||
{
|
||||
if ( const QgsCurvePolygon *polygon = qgsgeometry_cast< const QgsCurvePolygon * >( *partIt ) )
|
||||
{
|
||||
if ( polygon->exteriorRing() )
|
||||
{
|
||||
replaceIfBetter( polygon->exteriorRing()->startPoint(), partStartVertexNum );
|
||||
partStartVertexNum += polygon->exteriorRing()->numPoints();
|
||||
}
|
||||
for ( int i = 0; i < polygon->numInteriorRings(); ++i )
|
||||
{
|
||||
const QgsCurve *ring = polygon->interiorRing( i );
|
||||
replaceIfBetter( ring->startPoint(), partStartVertexNum );
|
||||
partStartVertexNum += ring->numPoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QgsPointLocator::Match m( QgsPointLocator::LineEndpoint, mLocator->mLayer, id, std::sqrt( mSrcPoint.sqrDist( bestPoint ) ), bestPoint, bestVertexNumber );
|
||||
// in range queries the filter may reject some matches
|
||||
if ( mFilter && !mFilter->acceptMatch( m ) )
|
||||
return;
|
||||
|
||||
if ( !mBest.isValid() || m.distance() < mBest.distance() )
|
||||
mBest = m;
|
||||
}
|
||||
|
||||
private:
|
||||
QgsPointLocator *mLocator = nullptr;
|
||||
QgsPointLocator::Match &mBest;
|
||||
QgsPointXY mSrcPoint;
|
||||
QgsPointLocator::MatchFilter *mFilter = nullptr;
|
||||
};
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@ -1209,6 +1315,21 @@ QgsPointLocator::Match QgsPointLocator::nearestMiddleOfSegment( const QgsPointXY
|
||||
return m;
|
||||
}
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestLineEndpoints( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !prepare( relaxed ) )
|
||||
return Match();
|
||||
|
||||
Match m;
|
||||
QgsPointLocator_VisitorNearestLineEndpoint visitor( this, m, point, filter );
|
||||
|
||||
QgsRectangle rect( point.x() - tolerance, point.y() - tolerance, point.x() + tolerance, point.y() + tolerance );
|
||||
mRTree->intersectsWithQuery( rect2region( rect ), visitor );
|
||||
if ( m.isValid() && m.distance() > tolerance )
|
||||
return Match(); // make sure that only match strictly within the tolerance is returned
|
||||
return m;
|
||||
}
|
||||
|
||||
QgsPointLocator::Match QgsPointLocator::nearestEdge( const QgsPointXY &point, double tolerance, MatchFilter *filter, bool relaxed )
|
||||
{
|
||||
if ( !prepare( relaxed ) )
|
||||
|
@ -153,12 +153,13 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
enum Type
|
||||
{
|
||||
Invalid = 0, //!< Invalid
|
||||
Vertex = 1, //!< Snapped to a vertex. Can be a vertex of the geometry or an intersection.
|
||||
Edge = 2, //!< Snapped to an edge
|
||||
Area = 4, //!< Snapped to an area
|
||||
Centroid = 8, //!< Snapped to a centroid
|
||||
MiddleOfSegment = 16, //!< Snapped to the middle of a segment
|
||||
All = Vertex | Edge | Area | Centroid | MiddleOfSegment //!< Combination of all types
|
||||
Vertex = 1 << 0, //!< Snapped to a vertex. Can be a vertex of the geometry or an intersection.
|
||||
Edge = 1 << 1, //!< Snapped to an edge
|
||||
Area = 1 << 2, //!< Snapped to an area
|
||||
Centroid = 1 << 3, //!< Snapped to a centroid
|
||||
MiddleOfSegment = 1 << 4, //!< Snapped to the middle of a segment
|
||||
LineEndpoint = 1 << 5, //!< Start or end points of lines only (since QGIS 3.20)
|
||||
All = Vertex | Edge | Area | Centroid | MiddleOfSegment //!< Combination of all types. Note LineEndpoint is not included as endpoints made redundant by the presence of the Vertex flag.
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS( Types, Type )
|
||||
@ -204,17 +205,24 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
QgsPointLocator::Type type() const { return mType; }
|
||||
|
||||
bool isValid() const { return mType != Invalid; }
|
||||
//! Returns true if the Match is a vertex
|
||||
//! Returns TRUE if the Match is a vertex
|
||||
bool hasVertex() const { return mType == Vertex; }
|
||||
//! Returns true if the Match is an edge
|
||||
//! Returns TRUE if the Match is an edge
|
||||
bool hasEdge() const { return mType == Edge; }
|
||||
//! Returns true if the Match is a centroid
|
||||
//! Returns TRUE if the Match is a centroid
|
||||
bool hasCentroid() const { return mType == Centroid; }
|
||||
//! Returns true if the Match is an area
|
||||
//! Returns TRUE if the Match is an area
|
||||
bool hasArea() const { return mType == Area; }
|
||||
//! Returns true if the Match is the middle of a segment
|
||||
//! Returns TRUE if the Match is the middle of a segment
|
||||
bool hasMiddleSegment() const { return mType == MiddleOfSegment; }
|
||||
|
||||
/**
|
||||
* Returns TRUE if the Match is a line endpoint (start or end vertex).
|
||||
*
|
||||
* \since QGIS 3.20
|
||||
*/
|
||||
bool hasLineEndpoint() const { return mType == LineEndpoint; }
|
||||
|
||||
/**
|
||||
* for vertex / edge match
|
||||
* units depending on what class returns it (geom.cache: layer units, map canvas snapper: dest crs units)
|
||||
@ -333,6 +341,14 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
*/
|
||||
Match nearestMiddleOfSegment( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Find nearest line endpoint (start or end vertex) to the specified point - up to distance specified by tolerance
|
||||
* Optional filter may discard unwanted matches.
|
||||
* This method is either blocking or non blocking according to \a relaxed parameter passed
|
||||
* \since 3.20
|
||||
*/
|
||||
Match nearestLineEndpoints( const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter = nullptr, bool relaxed = false );
|
||||
|
||||
/**
|
||||
* Find nearest edge to the specified point - up to distance specified by tolerance
|
||||
* Optional filter may discard unwanted matches.
|
||||
@ -474,6 +490,7 @@ class CORE_EXPORT QgsPointLocator : public QObject
|
||||
friend class TestQgsPointLocator;
|
||||
friend class QgsPointLocator_VisitorCentroidsInRect;
|
||||
friend class QgsPointLocator_VisitorMiddlesInRect;
|
||||
friend class QgsPointLocator_VisitorNearestLineEndpoint;
|
||||
};
|
||||
|
||||
|
||||
|
@ -163,7 +163,6 @@ bool QgsSnappingConfig::IndividualLayerSettings::operator ==( const QgsSnappingC
|
||||
&& mMaximumScale == other.mMaximumScale;
|
||||
}
|
||||
|
||||
|
||||
QgsSnappingConfig::QgsSnappingConfig( QgsProject *project )
|
||||
: mProject( project )
|
||||
{
|
||||
@ -280,6 +279,50 @@ QgsSnappingConfig::SnappingType QgsSnappingConfig::type() const
|
||||
return QgsSnappingConfig::SnappingType::Vertex;
|
||||
}
|
||||
|
||||
QString QgsSnappingConfig::snappingTypeFlagToString( SnappingTypeFlag type )
|
||||
{
|
||||
switch ( type )
|
||||
{
|
||||
case QgsSnappingConfig::NoSnapFlag:
|
||||
return QObject::tr( "No Snapping" );
|
||||
case QgsSnappingConfig::VertexFlag:
|
||||
return QObject::tr( "Vertex" );
|
||||
case QgsSnappingConfig::SegmentFlag:
|
||||
return QObject::tr( "Segment" );
|
||||
case QgsSnappingConfig::AreaFlag:
|
||||
return QObject::tr( "Area" );
|
||||
case QgsSnappingConfig::CentroidFlag:
|
||||
return QObject::tr( "Centroid" );
|
||||
case QgsSnappingConfig::MiddleOfSegmentFlag:
|
||||
return QObject::tr( "Middle of Segments" );
|
||||
case QgsSnappingConfig::LineEndpointFlag:
|
||||
return QObject::tr( "Line Endpoints" );
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QIcon QgsSnappingConfig::snappingTypeFlagToIcon( SnappingTypeFlag type )
|
||||
{
|
||||
switch ( type )
|
||||
{
|
||||
case QgsSnappingConfig::NoSnapFlag:
|
||||
return QIcon();
|
||||
case QgsSnappingConfig::VertexFlag:
|
||||
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnappingVertex.svg" ) );
|
||||
case QgsSnappingConfig::SegmentFlag:
|
||||
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnappingSegment.svg" ) );
|
||||
case QgsSnappingConfig::AreaFlag:
|
||||
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnappingArea.svg" ) );
|
||||
case QgsSnappingConfig::CentroidFlag:
|
||||
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnappingCentroid.svg" ) );
|
||||
case QgsSnappingConfig::MiddleOfSegmentFlag:
|
||||
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnappingMiddle.svg" ) );
|
||||
case QgsSnappingConfig::LineEndpointFlag:
|
||||
return QgsApplication::getThemeIcon( QStringLiteral( "/mIconSnappingEndpoint.svg" ) );
|
||||
}
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
void QgsSnappingConfig::setType( QgsSnappingConfig::SnappingType type )
|
||||
{
|
||||
switch ( type )
|
||||
@ -413,7 +456,7 @@ void QgsSnappingConfig::readProject( const QDomDocument &doc )
|
||||
mEnabled = snapSettingsElem.attribute( QStringLiteral( "enabled" ) ) == QLatin1String( "1" );
|
||||
|
||||
if ( snapSettingsElem.hasAttribute( QStringLiteral( "mode" ) ) )
|
||||
mMode = ( SnappingMode )snapSettingsElem.attribute( QStringLiteral( "mode" ) ).toInt();
|
||||
mMode = static_cast< SnappingMode >( snapSettingsElem.attribute( QStringLiteral( "mode" ) ).toInt() );
|
||||
|
||||
if ( snapSettingsElem.hasAttribute( QStringLiteral( "type" ) ) )
|
||||
{
|
||||
@ -424,7 +467,7 @@ void QgsSnappingConfig::readProject( const QDomDocument &doc )
|
||||
if ( versionElem.hasAttribute( QStringLiteral( "version" ) ) )
|
||||
{
|
||||
version = versionElem.attribute( QStringLiteral( "version" ) );
|
||||
QRegularExpression re( "([\\d]+)\\.([\\d]+)" );
|
||||
QRegularExpression re( QStringLiteral( "([\\d]+)\\.([\\d]+)" ) );
|
||||
QRegularExpressionMatch match = re.match( version );
|
||||
if ( match.hasMatch() )
|
||||
{
|
||||
@ -471,7 +514,7 @@ void QgsSnappingConfig::readProject( const QDomDocument &doc )
|
||||
mMaximumScale = snapSettingsElem.attribute( QStringLiteral( "maxScale" ) ).toDouble();
|
||||
|
||||
if ( snapSettingsElem.hasAttribute( QStringLiteral( "unit" ) ) )
|
||||
mUnits = ( QgsTolerance::UnitType )snapSettingsElem.attribute( QStringLiteral( "unit" ) ).toInt();
|
||||
mUnits = static_cast< QgsTolerance::UnitType >( snapSettingsElem.attribute( QStringLiteral( "unit" ) ).toInt() );
|
||||
|
||||
if ( snapSettingsElem.hasAttribute( QStringLiteral( "intersection-snapping" ) ) )
|
||||
mIntersectionSnapping = snapSettingsElem.attribute( QStringLiteral( "intersection-snapping" ) ) == QLatin1String( "1" );
|
||||
@ -499,7 +542,7 @@ void QgsSnappingConfig::readProject( const QDomDocument &doc )
|
||||
bool enabled = settingElement.attribute( QStringLiteral( "enabled" ) ) == QLatin1String( "1" );
|
||||
QgsSnappingConfig::SnappingTypeFlag type = static_cast<QgsSnappingConfig::SnappingTypeFlag>( settingElement.attribute( QStringLiteral( "type" ) ).toInt() );
|
||||
double tolerance = settingElement.attribute( QStringLiteral( "tolerance" ) ).toDouble();
|
||||
QgsTolerance::UnitType units = ( QgsTolerance::UnitType )settingElement.attribute( QStringLiteral( "units" ) ).toInt();
|
||||
QgsTolerance::UnitType units = static_cast< QgsTolerance::UnitType >( settingElement.attribute( QStringLiteral( "units" ) ).toInt() );
|
||||
double minScale = settingElement.attribute( QStringLiteral( "minScale" ) ).toDouble();
|
||||
double maxScale = settingElement.attribute( QStringLiteral( "maxScale" ) ).toDouble();
|
||||
|
||||
|
@ -70,11 +70,12 @@ class CORE_EXPORT QgsSnappingConfig
|
||||
enum SnappingTypes
|
||||
{
|
||||
NoSnapFlag = 0, //!< No snapping
|
||||
VertexFlag = 1, //!< On vertices
|
||||
SegmentFlag = 2, //!< On segments
|
||||
AreaFlag = 4, //!< On Area
|
||||
CentroidFlag = 8, //!< On centroid
|
||||
MiddleOfSegmentFlag = 16, //!< On Middle segment
|
||||
VertexFlag = 1 << 0, //!< On vertices
|
||||
SegmentFlag = 1 << 1, //!< On segments
|
||||
AreaFlag = 1 << 2, //!< On Area
|
||||
CentroidFlag = 1 << 3, //!< On centroid
|
||||
MiddleOfSegmentFlag = 1 << 4, //!< On Middle segment
|
||||
LineEndpointFlag = 1 << 5, //!< Start or end points of lines only (since QGIS 3.20)
|
||||
};
|
||||
Q_ENUM( SnappingTypes )
|
||||
Q_DECLARE_FLAGS( SnappingTypeFlag, SnappingTypes )
|
||||
@ -94,24 +95,19 @@ class CORE_EXPORT QgsSnappingConfig
|
||||
|
||||
/**
|
||||
* Convenient method to returns the translated name of the enum type
|
||||
* QgsSnappingConfig::SnappingTypeFlag
|
||||
* QgsSnappingConfig::SnappingTypeFlag.
|
||||
*
|
||||
* \since QGIS 3.12
|
||||
*/
|
||||
static const QString snappingTypeFlagToString( SnappingTypeFlag type )
|
||||
{
|
||||
switch ( type )
|
||||
{
|
||||
case QgsSnappingConfig::NoSnapFlag: return QObject::tr( "No Snapping" );
|
||||
case QgsSnappingConfig::VertexFlag: return QObject::tr( "Vertex" );
|
||||
case QgsSnappingConfig::SegmentFlag: return QObject::tr( "Segment" );
|
||||
case QgsSnappingConfig::AreaFlag: return QObject::tr( "Area" );
|
||||
case QgsSnappingConfig::CentroidFlag: return QObject::tr( "Centroid" );
|
||||
case QgsSnappingConfig::MiddleOfSegmentFlag: return QObject::tr( "Middle of Segments" );
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
static QString snappingTypeFlagToString( SnappingTypeFlag type );
|
||||
|
||||
/**
|
||||
* Convenient method to return an icon corresponding to the enum type
|
||||
* QgsSnappingConfig::SnappingTypeFlag.
|
||||
*
|
||||
* \since QGIS 3.20
|
||||
*/
|
||||
static QIcon snappingTypeFlagToIcon( SnappingTypeFlag type );
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
|
@ -180,13 +180,23 @@ static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointL
|
||||
return;
|
||||
|
||||
// ORDER
|
||||
// LineEndpoint
|
||||
// Vertex, Intersection
|
||||
// Middle
|
||||
// Centroid
|
||||
// Edge
|
||||
// Area
|
||||
|
||||
// First Vertex, or intersection
|
||||
// first line endpoint -- these are like vertex matches, but even more strict
|
||||
if ( ( bestMatch.type() & QgsPointLocator::LineEndpoint ) && !( candidateMatch.type() & QgsPointLocator::LineEndpoint ) )
|
||||
return;
|
||||
if ( candidateMatch.type() & QgsPointLocator::LineEndpoint )
|
||||
{
|
||||
bestMatch = candidateMatch;
|
||||
return;
|
||||
}
|
||||
|
||||
// Second Vertex, or intersection
|
||||
if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
|
||||
return;
|
||||
if ( candidateMatch.type() & QgsPointLocator::Vertex )
|
||||
@ -231,6 +241,10 @@ static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointX
|
||||
{
|
||||
_replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter ), tolerance );
|
||||
}
|
||||
if ( type & QgsPointLocator::LineEndpoint )
|
||||
{
|
||||
_replaceIfBetter( bestMatch, loc->nearestLineEndpoints( pointMap, tolerance, filter ), tolerance );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -700,7 +700,7 @@ bool QgsAdvancedDigitizingDockWidget::applyConstraints( QgsMapMouseEvent *e )
|
||||
*/
|
||||
e->setMapPoint( point );
|
||||
mSnapMatch = context.snappingUtils->snapToMap( point, nullptr, true );
|
||||
if ( ( mSnapMatch.hasVertex() && ( point == mSnapMatch.point() ) ) || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
|
||||
if ( ( ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) && ( point == mSnapMatch.point() ) ) || ( mSnapMatch.hasEdge() && QgsProject::instance()->topologicalEditing() ) )
|
||||
{
|
||||
e->snapPoint();
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ class GUI_EXPORT QgsAdvancedDigitizingDockWidget : public QgsDockWidget, private
|
||||
/**
|
||||
* Is it snapped to a vertex
|
||||
*/
|
||||
inline bool snappedToVertex() const { return ( mSnapMatch.isValid() && mSnapMatch.hasVertex() ); }
|
||||
inline bool snappedToVertex() const { return ( mSnapMatch.isValid() && ( mSnapMatch.hasVertex() || mSnapMatch.hasLineEndpoint() ) ); }
|
||||
|
||||
/**
|
||||
* Snapped to a segment
|
||||
|
@ -462,7 +462,7 @@ int QgsMapToolCapture::fetchLayerPoint( const QgsPointLocator::Match &match, Qgs
|
||||
{
|
||||
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( mCanvas->currentLayer() );
|
||||
QgsVectorLayer *sourceLayer = match.layer();
|
||||
if ( match.isValid() && ( match.hasVertex() || ( QgsProject::instance()->topologicalEditing() && ( match.hasEdge() || match.hasMiddleSegment() ) ) ) && sourceLayer &&
|
||||
if ( match.isValid() && ( match.hasVertex() || match.hasLineEndpoint() || ( QgsProject::instance()->topologicalEditing() && ( match.hasEdge() || match.hasMiddleSegment() ) ) ) && sourceLayer &&
|
||||
( sourceLayer->crs() == vlayer->crs() ) )
|
||||
{
|
||||
QgsFeature f;
|
||||
|
@ -97,6 +97,10 @@ void QgsSnapIndicator::setMatch( const QgsPointLocator::Match &match )
|
||||
{
|
||||
iconType = QgsVertexMarker::ICON_RHOMBUS; // area snap
|
||||
}
|
||||
else if ( match.hasLineEndpoint() )
|
||||
{
|
||||
iconType = QgsVertexMarker::ICON_BOX; // line endpoint snap
|
||||
}
|
||||
else // must be segment snap
|
||||
{
|
||||
iconType = QgsVertexMarker::ICON_DOUBLE_TRIANGLE;
|
||||
|
@ -437,7 +437,245 @@ class TestQgsSnappingUtils : public QObject
|
||||
QVERIFY( m2.isValid() );
|
||||
QCOMPARE( m2.type(), QgsPointLocator::Centroid );
|
||||
QCOMPARE( m2.point(), QgsPointXY( 2.5, 2.5 ) );
|
||||
}
|
||||
|
||||
void testSnapOnLineEndpoints()
|
||||
{
|
||||
std::unique_ptr<QgsVectorLayer> vSnapCentroidMiddle( new QgsVectorLayer( QStringLiteral( "LineString" ), QStringLiteral( "m" ), QStringLiteral( "memory" ) ) );
|
||||
QgsFeature f1;
|
||||
QgsGeometry f1g = QgsGeometry::fromWkt( "LineString (0 0, 0 5, 5 5, 5 0)" );
|
||||
f1.setGeometry( f1g );
|
||||
|
||||
QgsFeatureList flist;
|
||||
flist << f1;
|
||||
vSnapCentroidMiddle->dataProvider()->addFeatures( flist );
|
||||
QVERIFY( vSnapCentroidMiddle->dataProvider()->featureCount() == 1 );
|
||||
|
||||
QgsMapSettings mapSettings;
|
||||
mapSettings.setOutputSize( QSize( 100, 100 ) );
|
||||
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
|
||||
QVERIFY( mapSettings.hasValidSettings() );
|
||||
|
||||
QgsSnappingUtils u;
|
||||
u.setMapSettings( mapSettings );
|
||||
QgsSnappingConfig snappingConfig = u.config();
|
||||
snappingConfig.setEnabled( true );
|
||||
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
|
||||
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::LineEndpointFlag ), 0.2, QgsTolerance::ProjectUnits, 0.0, 0.0 );
|
||||
snappingConfig.setIndividualLayerSettings( vSnapCentroidMiddle.get(), layerSettings );
|
||||
u.setConfig( snappingConfig );
|
||||
|
||||
// snap to start
|
||||
QgsPointLocator::Match m = u.snapToMap( QgsPointXY( 0, -0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 0.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 0 );
|
||||
|
||||
// snap to end
|
||||
m = u.snapToMap( QgsPointXY( 5, -0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 5.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 3 );
|
||||
|
||||
// try to snap to a non start/end vertex
|
||||
QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( -0.1, 5 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
}
|
||||
|
||||
void testSnapOnLineEndpointsMultiLine()
|
||||
{
|
||||
std::unique_ptr<QgsVectorLayer> vSnapCentroidMiddle( new QgsVectorLayer( QStringLiteral( "MultiLineString" ), QStringLiteral( "m" ), QStringLiteral( "memory" ) ) );
|
||||
QgsFeature f1;
|
||||
QgsGeometry f1g = QgsGeometry::fromWkt( "MultiLineString ((0 0, 0 5, 5 5, 5 0), (0 -0.1, 0 -5, 5 -0.5))" );
|
||||
f1.setGeometry( f1g );
|
||||
|
||||
QgsFeatureList flist;
|
||||
flist << f1;
|
||||
vSnapCentroidMiddle->dataProvider()->addFeatures( flist );
|
||||
QVERIFY( vSnapCentroidMiddle->dataProvider()->featureCount() == 1 );
|
||||
|
||||
QgsMapSettings mapSettings;
|
||||
mapSettings.setOutputSize( QSize( 100, 100 ) );
|
||||
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
|
||||
QVERIFY( mapSettings.hasValidSettings() );
|
||||
|
||||
QgsSnappingUtils u;
|
||||
u.setMapSettings( mapSettings );
|
||||
QgsSnappingConfig snappingConfig = u.config();
|
||||
snappingConfig.setEnabled( true );
|
||||
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
|
||||
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::LineEndpointFlag ), 0.2, QgsTolerance::ProjectUnits, 0.0, 0.0 );
|
||||
snappingConfig.setIndividualLayerSettings( vSnapCentroidMiddle.get(), layerSettings );
|
||||
u.setConfig( snappingConfig );
|
||||
|
||||
// snap to start
|
||||
QgsPointLocator::Match m = u.snapToMap( QgsPointXY( 0, 0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 0.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 0 );
|
||||
|
||||
m = u.snapToMap( QgsPointXY( 0, -0.07 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 0.0, -0.1 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 4 );
|
||||
|
||||
// snap to end
|
||||
m = u.snapToMap( QgsPointXY( 5, -0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 5.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 3 );
|
||||
|
||||
m = u.snapToMap( QgsPointXY( 5, -0.4 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 5.0, -0.5 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 6 );
|
||||
|
||||
// try to snap to a non start/end vertex
|
||||
QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( -0.1, 5 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
|
||||
m2 = u.snapToMap( QgsPointXY( 0, -5 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
}
|
||||
|
||||
void testSnapOnPolygonEndpoints()
|
||||
{
|
||||
std::unique_ptr<QgsVectorLayer> vSnapCentroidMiddle( new QgsVectorLayer( QStringLiteral( "Polygon" ), QStringLiteral( "m" ), QStringLiteral( "memory" ) ) );
|
||||
QgsFeature f1;
|
||||
QgsGeometry f1g = QgsGeometry::fromWkt( "Polygon ((1 0, 0 5, 5 5, 5 0, 1 0),(3 2, 3.5 2, 3.5 3, 3 2))" );
|
||||
f1.setGeometry( f1g );
|
||||
|
||||
QgsFeatureList flist;
|
||||
flist << f1;
|
||||
vSnapCentroidMiddle->dataProvider()->addFeatures( flist );
|
||||
QVERIFY( vSnapCentroidMiddle->dataProvider()->featureCount() == 1 );
|
||||
|
||||
QgsMapSettings mapSettings;
|
||||
mapSettings.setOutputSize( QSize( 100, 100 ) );
|
||||
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
|
||||
QVERIFY( mapSettings.hasValidSettings() );
|
||||
|
||||
QgsSnappingUtils u;
|
||||
u.setMapSettings( mapSettings );
|
||||
QgsSnappingConfig snappingConfig = u.config();
|
||||
snappingConfig.setEnabled( true );
|
||||
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
|
||||
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::LineEndpointFlag ), 0.2, QgsTolerance::ProjectUnits, 0.0, 0.0 );
|
||||
snappingConfig.setIndividualLayerSettings( vSnapCentroidMiddle.get(), layerSettings );
|
||||
u.setConfig( snappingConfig );
|
||||
|
||||
// snap to start of exterior
|
||||
QgsPointLocator::Match m = u.snapToMap( QgsPointXY( 1, -0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 1.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 0 );
|
||||
|
||||
// snap to ring start
|
||||
m = u.snapToMap( QgsPointXY( 3, 2.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 3.0, 2.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 5 );
|
||||
|
||||
// try to snap to a non start/end vertex
|
||||
QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( -0.1, 5 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
m2 = u.snapToMap( QgsPointXY( 3.51, 3 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
}
|
||||
|
||||
void testSnapOnMultiPolygonEndpoints()
|
||||
{
|
||||
std::unique_ptr<QgsVectorLayer> vSnapCentroidMiddle( new QgsVectorLayer( QStringLiteral( "MultiPolygon" ), QStringLiteral( "m" ), QStringLiteral( "memory" ) ) );
|
||||
QgsFeature f1;
|
||||
QgsGeometry f1g = QgsGeometry::fromWkt( "MultiPolygon (((1 0, 0 5, 5 5, 5 0, 1 0),(3 2, 3.5 2, 3.5 3, 3 2)), ((10 0, 10 5, 15 5, 15 0, 10 0),(13 2, 13.5 2, 13.5 3, 13 2)) )" );
|
||||
f1.setGeometry( f1g );
|
||||
|
||||
QgsFeatureList flist;
|
||||
flist << f1;
|
||||
vSnapCentroidMiddle->dataProvider()->addFeatures( flist );
|
||||
QVERIFY( vSnapCentroidMiddle->dataProvider()->featureCount() == 1 );
|
||||
|
||||
QgsMapSettings mapSettings;
|
||||
mapSettings.setOutputSize( QSize( 100, 100 ) );
|
||||
mapSettings.setExtent( QgsRectangle( 0, 0, 10, 10 ) );
|
||||
QVERIFY( mapSettings.hasValidSettings() );
|
||||
|
||||
QgsSnappingUtils u;
|
||||
u.setMapSettings( mapSettings );
|
||||
QgsSnappingConfig snappingConfig = u.config();
|
||||
snappingConfig.setEnabled( true );
|
||||
snappingConfig.setMode( QgsSnappingConfig::AdvancedConfiguration );
|
||||
QgsSnappingConfig::IndividualLayerSettings layerSettings( true, static_cast<QgsSnappingConfig::SnappingTypeFlag>( QgsSnappingConfig::LineEndpointFlag ), 0.2, QgsTolerance::ProjectUnits, 0.0, 0.0 );
|
||||
snappingConfig.setIndividualLayerSettings( vSnapCentroidMiddle.get(), layerSettings );
|
||||
u.setConfig( snappingConfig );
|
||||
|
||||
// snap to start of exterior
|
||||
QgsPointLocator::Match m = u.snapToMap( QgsPointXY( 1, -0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 1.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 0 );
|
||||
|
||||
m = u.snapToMap( QgsPointXY( 10, -0.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 10.0, 0.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 9 );
|
||||
|
||||
// snap to ring start
|
||||
m = u.snapToMap( QgsPointXY( 3, 2.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 3.0, 2.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 5 );
|
||||
|
||||
m = u.snapToMap( QgsPointXY( 13, 2.1 ) );
|
||||
QVERIFY( m.isValid() );
|
||||
QCOMPARE( m.type(), QgsPointLocator::LineEndpoint );
|
||||
QCOMPARE( m.point(), QgsPointXY( 13.0, 2.0 ) );
|
||||
QVERIFY( m.hasLineEndpoint() );
|
||||
QVERIFY( !m.hasEdge() );
|
||||
QCOMPARE( m.vertexIndex(), 14 );
|
||||
|
||||
// try to snap to a non start/end vertex
|
||||
QgsPointLocator::Match m2 = u.snapToMap( QgsPointXY( -0.1, 5 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
m2 = u.snapToMap( QgsPointXY( 3.51, 3 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
m2 = u.snapToMap( QgsPointXY( 10, 5 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
m2 = u.snapToMap( QgsPointXY( 13.51, 3 ) );
|
||||
QVERIFY( !m2.isValid() );
|
||||
}
|
||||
|
||||
void testSnapOnCurrentLayer()
|
||||
|
Loading…
x
Reference in New Issue
Block a user