Skip to content
Browse files
Merge pull request #2665 from wonder-sk/auto-trace
[FEATURE] Tracing of features (digitizing)

Tracing can be now used in various capturing map tools (add feature, add part, ...) including reshape and split tools.

Tracing is simply a new mode for these tools - when tracing is not enabled, the tools work as usual. When tracing is enabled (by clicking the new magnet icon or pressing T key), tools switch to tracing behavior:
- first click on a vertex/edge (must be snapped!) will start tracing - moving mouse on top of the map continuously updates the trace
- next click will confirm the trace and mark start of a new trace Tracing can be enabled/disabled anytime even while digitizing one feature, so it is possible to digitize some parts of the feature with tracing enabled and other parts with tracing disabled.

Tracing respects snapping configuration for the list of traceable layers.

If there are too many features in map display, tracing is disabled to avoid potentially long tracing structure preparation and large memory overhead. After zooming in or disabling some layers the tracing is enabled again.

Internally, things work like this:
- when tracing is requested, linestrings are extracted from vector layers, then noded (using GEOSNode to resolve all intersections) and finally a simple planar graph is built (vertices + edges)
- when tracing, endpoints are temporarily added to the graph (if not equal to one of existing vertices already) and Dijkstra's algorithm is run to get shortest path

Original specs for the curious ones (the interaction with QGIS is slightly improved from what has been specified):
  • Loading branch information
wonder-sk committed Jan 13, 2016
2 parents b0bfa5f + 17f85f6 commit b83f6e359a876615fe17b914a04c42fe92a155b3
@@ -535,6 +535,7 @@
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
Binary file not shown.

Large diffs are not rendered by default.

@@ -120,6 +120,7 @@
%Include qgsstatisticalsummary.sip
%Include qgsstringutils.sip
%Include qgstolerance.sip
%Include qgstracer.sip
%Include qgsvectordataprovider.sip
%Include qgsvectorfilewriter.sip
%Include qgsvectorlayer.sip
@@ -59,15 +59,18 @@ class QgsSnappingUtils : QObject
/** Find out which strategy is used for indexing - by default hybrid indexing is used */
IndexingStrategy indexingStrategy() const;

/** Configure options used when the mode is snap to current layer */
/** Configure options used when the mode is snap to current layer or to all layers */
void setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit );
/** Query options used when the mode is snap to current layer */
/** Query options used when the mode is snap to current layer or to all layers */
void defaultSettings( int& type /Out/, double& tolerance /Out/, QgsTolerance::UnitType& unit /Out/ );

struct LayerConfig
LayerConfig( QgsVectorLayer* l, const QgsPointLocator::Types& t, double tol, QgsTolerance::UnitType u );

bool operator==( const QgsSnappingUtils::LayerConfig& other ) const;
bool operator!=( const QgsSnappingUtils::LayerConfig& other ) const;

QgsVectorLayer* layer;
QgsPointLocator::Types type;
double tolerance;
@@ -88,6 +91,12 @@ class QgsSnappingUtils : QObject
/** Read snapping configuration from the project */
void readConfigFromProject();

/** Emitted when snapping configuration has been changed
* @note added in QGIS 2.14
void configChanged();

//! Called when starting to index - can be overridden and e.g. progress dialog can be provided
virtual void prepareIndexStarting( int count );
@@ -0,0 +1,74 @@
/** \ingroup core
* Utility class that construct a planar graph from the input vector
* layers and provides shortest path search for tracing of existing
* features.
* @note added in QGIS 2.14
class QgsTracer : QObject
#include <qgstracer.h>


//! Get layers used for tracing
QList<QgsVectorLayer*> layers() const;
//! Set layers used for tracing
void setLayers( const QList<QgsVectorLayer*>& layers );

//! Get CRS used for tracing
QgsCoordinateReferenceSystem destinationCrs() const;
//! Set CRS used for tracing
void setDestinationCrs( const QgsCoordinateReferenceSystem& crs );

//! Get extent to which graph's features will be limited (empty extent means no limit)
QgsRectangle extent() const;
//! Set extent to which graph's features will be limited (empty extent means no limit)
void setExtent( const QgsRectangle& extent );

//! Get maximum possible number of features in graph. If the number is exceeded, graph is not created.
int maxFeatureCount() const;
//! Get maximum possible number of features in graph. If the number is exceeded, graph is not created.
void setMaxFeatureCount( int count );

//! Build the internal data structures. This may take some time
//! depending on how big the input layers are. It is not necessary
//! to call this method explicitly - it will be called by findShortestPath()
//! if necessary.
bool init();

//! Whether the internal data structures have been initialized
bool isInitialized() const;

//! Possible errors that may happen when calling findShortestPath()
enum PathError
ErrNone, //!< No error
ErrTooManyFeatures, //!< Max feature count treshold was reached while reading features
ErrPoint1, //!< Start point cannot be joined to the graph
ErrPoint2, //!< End point cannot be joined to the graph
ErrNoPath, //!< Points are not connected in the graph

//! Given two points, find the shortest path and return points on the way.
//! The optional "error" argument may receive error code (PathError enum) if it is not null
//! @return array of points - trace of linestrings of other features (empty array one error)
QVector<QgsPoint> findShortestPath( const QgsPoint& p1, const QgsPoint& p2, QgsTracer::PathError* error /Out/ = nullptr );

//! Find out whether the point is snapped to a vertex or edge (i.e. it can be used for tracing start/stop)
bool isPointSnapped( const QgsPoint& pt );

//! Allows derived classes to setup the settings just before the tracer is initialized.
//! This allows the configuration to be set in a lazy way only when it is really necessary.
//! Default implementation does nothing.
virtual void configure();

protected slots:
//! Destroy the existing graph structure if any (de-initialize)
void invalidateGraph();
@@ -93,6 +93,7 @@
%Include qgsmapcanvasmap.sip
%Include qgsmapcanvassnapper.sip
%Include qgsmapcanvassnappingutils.sip
%Include qgsmapcanvastracer.sip
%Include qgsmaplayeractionregistry.sip
%Include qgsmaplayercombobox.sip
%Include qgsmaplayermodel.sip
@@ -0,0 +1,38 @@
/** \ingroup gui
* Extension of QgsTracer that provides extra functionality:
* - automatic updates of own configuration based on canvas settings
* - reporting of issues to the user via message bar
* A simple registry of tracer instances associated to map canvas instances
* is kept for convenience. (Map tools do not need to create their local
* tracer instances and map canvas API is not "polluted" by this optional
* functionality).
* @note added in QGIS 2.14
class QgsMapCanvasTracer : QgsTracer
#include <qgsmapcanvastracer.h>

//! Create tracer associated with a particular map canvas, optionally message bar for reporting
explicit QgsMapCanvasTracer( QgsMapCanvas* canvas, QgsMessageBar* messageBar = 0 );

//! Access to action that user may use to toggle tracing on/off
QAction* actionEnableTracing();

//! Retrieve instance of this class associated with given canvas (if any).
//! The class keeps a simple registry of tracers associated with map canvas
//! instances for easier access to the common tracer by various map tools
static QgsMapCanvasTracer* tracerForCanvas( QgsMapCanvas* canvas );

//! Report a path finding error to the user
void reportError( QgsTracer::PathError err, bool addingVertex );

//! Sets configuration from current snapping settings and canvas settings
virtual void configure();
@@ -164,6 +164,7 @@
#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmapcanvassnappingutils.h"
#include "qgsmapcanvastracer.h"
#include "qgsmaplayer.h"
#include "qgsmaplayerregistry.h"
#include "qgsmaplayerstyleguiutils.h"
@@ -556,6 +557,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, QWidget * parent,
, mComposerManager( nullptr )
, mpTileScaleWidget( nullptr )
, mpGpsWidget( nullptr )
, mTracer( nullptr )
, mSnappingUtils( nullptr )
, mProjectLastModified()
, mWelcomePage( nullptr )
@@ -1024,6 +1026,7 @@ QgisApp::QgisApp()
, mMacrosWarn( nullptr )
, mUserInputDockWidget( nullptr )
, mVectorLayerTools( nullptr )
, mTracer( nullptr )
, mActionFilterLegend( nullptr )
, mLegendExpressionFilterButton( nullptr )
, mSnappingUtils( nullptr )
@@ -1104,6 +1107,8 @@ QgisApp::~QgisApp()

delete mComposerManager;

delete mTracer;

delete mVectorLayerTools;
delete mWelcomePage;

@@ -1975,6 +1980,9 @@ void QgisApp::createToolBars()

// Cad toolbar
mAdvancedDigitizeToolBar->insertAction( mActionUndo, mAdvancedDigitizingDockWidget->enableAction() );

mTracer = new QgsMapCanvasTracer( mMapCanvas, messageBar() );
mAdvancedDigitizeToolBar->insertAction( mActionUndo, mTracer->actionEnableTracing() );

void QgisApp::createStatusBar()
@@ -9681,6 +9689,7 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
mActionMergeFeatures->setEnabled( false );
mActionMergeFeatureAttributes->setEnabled( false );
mActionRotatePointSymbols->setEnabled( false );
mTracer->actionEnableTracing()->setEnabled( false );

mActionPinLabels->setEnabled( false );
mActionShowHideLabels->setEnabled( false );
@@ -9801,6 +9810,9 @@ void QgisApp::activateDeactivateLayerRelatedActions( QgsMapLayer* layer )
mActionRotateFeature->setEnabled( isEditable && canChangeGeometry );
mActionNodeTool->setEnabled( isEditable && canChangeGeometry );

mTracer->actionEnableTracing()->setEnabled( isEditable && canAddFeatures &&
( vlayer->geometryType() == QGis::Line || vlayer->geometryType() == QGis::Polygon ) );

if ( vlayer->geometryType() == QGis::Point )
mActionAddFeature->setIcon( QgsApplication::getThemeIcon( "/mActionCapturePoint.svg" ) );
@@ -81,6 +81,7 @@ class QgsAdvancedDigitizingDockWidget;
class QgsSnappingDialog;
class QgsGPSInformationWidget;
class QgsStatisticalSummaryDockWidget;
class QgsMapCanvasTracer;

class QgsDecorationItem;

@@ -1698,6 +1699,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow

QgsVectorLayerTools* mVectorLayerTools;

//! A class that facilitates tracing of features
QgsMapCanvasTracer* mTracer;

QAction* mActionFilterLegend;

QgsLegendFilterButton* mLegendExpressionFilterButton;
@@ -184,6 +184,7 @@ SET(QGIS_CORE_SRCS
@@ -455,6 +456,7 @@ SET(QGIS_CORE_MOC_HDRS
@@ -660,6 +662,7 @@ SET(QGIS_CORE_HDRS

@@ -369,15 +369,35 @@ void QgsSnappingUtils::setMapSettings( const QgsMapSettings& settings )

void QgsSnappingUtils::setCurrentLayer( QgsVectorLayer* layer )
mCurrentLayer = layer;

void QgsSnappingUtils::setSnapToMapMode( QgsSnappingUtils::SnapToMapMode mode )
if ( mSnapToMapMode == mode )

mSnapToMapMode = mode;
emit configChanged();

void QgsSnappingUtils::setDefaultSettings( int type, double tolerance, QgsTolerance::UnitType unit )
// force map units - can't use layer units for just any layer
if ( unit == QgsTolerance::LayerUnits )
unit = QgsTolerance::ProjectUnits;

if ( mDefaultType == type && mDefaultTolerance == tolerance && mDefaultUnit == unit )

mDefaultType = type;
mDefaultTolerance = tolerance;
mDefaultUnit = unit;

if ( mSnapToMapMode != SnapAdvanced ) // does not affect advanced mode
emit configChanged();

void QgsSnappingUtils::defaultSettings( int& type, double& tolerance, QgsTolerance::UnitType& unit )
@@ -387,6 +407,25 @@ void QgsSnappingUtils::defaultSettings( int& type, double& tolerance, QgsToleran
unit = mDefaultUnit;

void QgsSnappingUtils::setLayers( const QList<QgsSnappingUtils::LayerConfig>& layers )
if ( mLayers == layers )

mLayers = layers;
if ( mSnapToMapMode == SnapAdvanced ) // only affects advanced mode
emit configChanged();

void QgsSnappingUtils::setSnapOnIntersections( bool enabled )
if ( mSnapOnIntersection == enabled )

mSnapOnIntersection = enabled;
emit configChanged();

const QgsCoordinateReferenceSystem* QgsSnappingUtils::destCRS()
return mMapSettings.hasCrsTransformEnabled() ? &mMapSettings.destinationCrs() : nullptr;
@@ -467,6 +506,7 @@ void QgsSnappingUtils::readConfigFromProject()
mLayers.append( LayerConfig( vlayer, t, tolIt->toDouble(), static_cast< QgsTolerance::UnitType >( tolUnitIt->toInt() ) ) );

emit configChanged();

void QgsSnappingUtils::onLayersWillBeRemoved( const QStringList& layerIds )

0 comments on commit b83f6e3

Please sign in to comment.