QGIS API Documentation  2.13.0-Master
qgsmapcanvassnapper.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmapcanvassnapper.cpp
3  -----------------------
4  begin : June 21, 2007
5  copyright : (C) 2007 by Marco Hugentobler
6  email : marco dot hugentobler at karto dot baug dot ethz dot ch
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsmapcanvassnapper.h"
19 #include "qgsmapcanvas.h"
20 #include "qgsmaplayerregistry.h"
21 #include "qgsmaptopixel.h"
22 #include "qgsproject.h"
23 #include "qgsvectorlayer.h"
24 #include "qgstolerance.h"
25 #include <QSettings>
26 #include "qgslogger.h"
27 #include "qgsgeometry.h"
28 
30  : mMapCanvas( canvas )
31  , mSnapper( nullptr )
32 {
33  if ( !canvas )
34  return;
35 
36  mSnapper = new QgsSnapper( canvas->mapSettings() );
37 }
38 
39 QgsMapCanvasSnapper::QgsMapCanvasSnapper(): mMapCanvas( nullptr ), mSnapper( nullptr )
40 {
41 }
42 
44 {
45  delete mSnapper;
46 }
47 
49 {
50  mMapCanvas = canvas;
51  delete mSnapper;
52  if ( mMapCanvas )
53  {
54  mSnapper = new QgsSnapper( canvas->mapSettings() );
55  }
56  else
57  {
58  mSnapper = nullptr;
59  }
60 }
61 
64  double snappingTol,
65  const QList<QgsPoint>& excludePoints,
66  bool allResutInTolerance )
67 {
68  results.clear();
69 
70  if ( !mSnapper || !mMapCanvas )
71  return 1;
72 
73  //topological editing on?
74  int topologicalEditing = QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 );
75  if ( allResutInTolerance )
76  {
78  }
79  else if ( topologicalEditing == 0 )
80  {
82  }
83  else
84  {
86  }
87 
88  //current vector layer
89  QgsMapLayer* currentLayer = mMapCanvas->currentLayer();
90  if ( !currentLayer )
91  return 2;
92 
93  QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer *>( currentLayer );
94  if ( !vlayer )
95  return 3;
96 
97  QgsSnapper::SnapLayer snapLayer;
98  snapLayer.mLayer = vlayer;
99  snapLayer.mSnapTo = snap_to;
101 
102  if ( snappingTol < 0 )
103  {
104  //use search tolerance for vertex editing
105  snapLayer.mTolerance = QgsTolerance::vertexSearchRadius( vlayer, mMapCanvas->mapSettings() );
106  }
107  else
108  {
109  snapLayer.mTolerance = snappingTol;
110  }
111 
112  QList<QgsSnapper::SnapLayer> snapLayers;
113  snapLayers.append( snapLayer );
114  mSnapper->setSnapLayers( snapLayers );
115 
116  QgsPoint mapPoint = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( p );
117  if ( mSnapper->snapMapPoint( mapPoint, results, excludePoints ) != 0 )
118  return 4;
119 
120  return 0;
121 }
122 
124 {
125  const QgsPoint mapCoordPoint = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( p.x(), p.y() );
126  return snapToBackgroundLayers( mapCoordPoint, results, excludePoints );
127 }
128 
130 {
131  results.clear();
132 
133  if ( !mSnapper )
134  return 5;
135 
136  //topological editing on?
137  int topologicalEditing = QgsProject::instance()->readNumEntry( "Digitizing", "/TopologicalEditing", 0 );
138 
139  //snapping on intersection on?
140  int intersectionSnapping = QgsProject::instance()->readNumEntry( "Digitizing", "/IntersectionSnapping", 0 );
141 
142  if ( topologicalEditing == 0 )
143  {
144  if ( intersectionSnapping == 0 )
146  else
148  }
149  else if ( intersectionSnapping == 0 )
150  {
152  }
153  else
154  {
156  }
157 
158  QgsVectorLayer* currentVectorLayer = dynamic_cast<QgsVectorLayer*>( mMapCanvas->currentLayer() );
159  if ( !currentVectorLayer )
160  {
161  return 1;
162  }
163 
164  //read snapping settings from project
165  QStringList layerIdList, enabledList, toleranceList, toleranceUnitList, snapToList;
166 
167  bool ok, snappingDefinedInProject;
168 
169  QSettings settings;
170  QString snappingMode = QgsProject::instance()->readEntry( "Digitizing", "/SnappingMode", "current_layer", &snappingDefinedInProject );
171  QString defaultSnapToleranceUnit = snappingDefinedInProject ? QgsProject::instance()->readEntry( "Digitizing", "/DefaultSnapToleranceUnit" ) : settings.value( "/qgis/digitizing/default_snapping_tolerance_unit", "0" ).toString();
172  QString defaultSnapType = snappingDefinedInProject ? QgsProject::instance()->readEntry( "Digitizing", "/DefaultSnapType" ) : settings.value( "/qgis/digitizing/default_snap_mode", "off" ).toString();
173  QString defaultSnapTolerance = snappingDefinedInProject ? QString::number( QgsProject::instance()->readDoubleEntry( "Digitizing", "/DefaultSnapTolerance" ) ) : settings.value( "/qgis/digitizing/default_snapping_tolerance", "0" ).toString();
174 
175  if ( !snappingDefinedInProject && defaultSnapType == "off" )
176  {
177  return 0;
178  }
179 
180  if ( snappingMode == "current_layer" || !snappingDefinedInProject )
181  {
182  layerIdList.append( currentVectorLayer->id() );
183  enabledList.append( "enabled" );
184  toleranceList.append( defaultSnapTolerance );
185  toleranceUnitList.append( defaultSnapToleranceUnit );
186  snapToList.append( defaultSnapType );
187  }
188  else if ( snappingMode == "all_layers" )
189  {
190  QList<QgsMapLayer*> allLayers = mMapCanvas->layers();
191  QList<QgsMapLayer*>::const_iterator layerIt = allLayers.constBegin();
192  for ( ; layerIt != allLayers.constEnd(); ++layerIt )
193  {
194  if ( !( *layerIt ) )
195  {
196  continue;
197  }
198  layerIdList.append(( *layerIt )->id() );
199  enabledList.append( "enabled" );
200  toleranceList.append( defaultSnapTolerance );
201  toleranceUnitList.append( defaultSnapToleranceUnit );
202  snapToList.append( defaultSnapType );
203  }
204  }
205  else //advanced
206  {
207  layerIdList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingList", QStringList(), &ok );
208  enabledList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingEnabledList", QStringList(), &ok );
209  toleranceList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceList", QStringList(), &ok );
210  toleranceUnitList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnappingToleranceUnitList", QStringList(), &ok );
211  snapToList = QgsProject::instance()->readListEntry( "Digitizing", "/LayerSnapToList", QStringList(), &ok );
212  }
213 
214  if ( !( layerIdList.size() == enabledList.size() &&
215  layerIdList.size() == toleranceList.size() &&
216  layerIdList.size() == toleranceUnitList.size() &&
217  layerIdList.size() == snapToList.size() ) )
218  {
219  // lists must have the same size, otherwise something is wrong
220  return 1;
221  }
222 
223  QList<QgsSnapper::SnapLayer> snapLayers;
224  QgsSnapper::SnapLayer snapLayer;
225 
226 
227 
228  // set layers, tolerances, snap to segment/vertex to QgsSnapper
229  QStringList::const_iterator layerIt( layerIdList.constBegin() );
230  QStringList::const_iterator tolIt( toleranceList.constBegin() );
231  QStringList::const_iterator tolUnitIt( toleranceUnitList.constBegin() );
232  QStringList::const_iterator snapIt( snapToList.constBegin() );
233  QStringList::const_iterator enabledIt( enabledList.constBegin() );
234  for ( ; layerIt != layerIdList.constEnd(); ++layerIt, ++tolIt, ++tolUnitIt, ++snapIt, ++enabledIt )
235  {
236  if ( *enabledIt != "enabled" )
237  {
238  // skip layer if snapping is not enabled
239  continue;
240  }
241 
242  //layer
243  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsMapLayerRegistry::instance()->mapLayer( *layerIt ) );
244  if ( !vlayer || !vlayer->hasGeometryType() )
245  continue;
246 
247  snapLayer.mLayer = vlayer;
248 
249  //tolerance
250  snapLayer.mTolerance = tolIt->toDouble();
251  snapLayer.mUnitType = ( QgsTolerance::UnitType ) tolUnitIt->toInt();
252 
253  // segment or vertex
254  if ( *snapIt == "to vertex" || *snapIt == "to_vertex" )
255  {
256  snapLayer.mSnapTo = QgsSnapper::SnapToVertex;
257  }
258  else if ( *snapIt == "to segment" || *snapIt == "to_segment" )
259  {
261  }
262  else if ( *snapIt == "to vertex and segment" || *snapIt == "to_vertex_and_segment" )
263  {
265  }
266  else //off
267  {
268  continue;
269  }
270 
271  snapLayers.append( snapLayer );
272  }
273 
274  mSnapper->setSnapLayers( snapLayers );
275 
276  if ( mSnapper->snapMapPoint( point, results, excludePoints ) != 0 )
277  return 4;
278 
279  if ( intersectionSnapping != 1 )
280  return 0;
281 
285  it != results.constEnd();
286  ++it )
287  {
288  if ( it->snappedVertexNr == -1 )
289  {
290  QgsDebugMsg( "segment" );
291  segments.push_back( *it );
292  }
293  else
294  {
295  QgsDebugMsg( "no segment" );
296  points.push_back( *it );
297  }
298  }
299 
300  if ( segments.count() < 2 )
301  return 0;
302 
303  QList<QgsSnappingResult> myResults;
304 
305  for ( QVector<QgsSnappingResult>::const_iterator oSegIt = segments.constBegin();
306  oSegIt != segments.constEnd();
307  ++oSegIt )
308  {
309  QgsDebugMsg( QString::number( oSegIt->beforeVertexNr ) );
310 
311  QVector<QgsPoint> vertexPoints;
312  vertexPoints.append( oSegIt->beforeVertex );
313  vertexPoints.append( oSegIt->afterVertex );
314 
315  QgsGeometry* lineA = QgsGeometry::fromPolyline( vertexPoints );
316 
317  for ( QVector<QgsSnappingResult>::iterator iSegIt = segments.begin();
318  iSegIt != segments.end();
319  ++iSegIt )
320  {
321  QVector<QgsPoint> vertexPoints;
322  vertexPoints.append( iSegIt->beforeVertex );
323  vertexPoints.append( iSegIt->afterVertex );
324 
325  QgsGeometry* lineB = QgsGeometry::fromPolyline( vertexPoints );
326  QgsGeometry* intersectionPoint = lineA->intersection( lineB );
327  delete lineB;
328 
329  if ( intersectionPoint && intersectionPoint->type() == QGis::Point )
330  {
331  //We have to check the intersection point is inside the tolerance distance for both layers
332  double toleranceA = 0;
333  double toleranceB = 0;
334  for ( int i = 0 ;i < snapLayers.size();++i )
335  {
336  if ( snapLayers[i].mLayer == oSegIt->layer )
337  {
338  toleranceA = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
339  }
340  if ( snapLayers[i].mLayer == iSegIt->layer )
341  {
342  toleranceB = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
343  }
344  }
345  QgsGeometry* cursorPoint = QgsGeometry::fromPoint( point );
346  double distance = intersectionPoint->distance( *cursorPoint );
347  if ( distance < toleranceA && distance < toleranceB )
348  {
349  iSegIt->snappedVertex = intersectionPoint->asPoint();
350  myResults.append( *iSegIt );
351  }
352  delete cursorPoint;
353  }
354  delete intersectionPoint;
355 
356  }
357 
358  delete lineA;
359  }
360 
361  if ( myResults.length() > 0 )
362  {
363  results.clear();
364  results = myResults;
365  }
366 
367  return 0;
368 }
void clear()
Base class for all map layer types.
Definition: qgsmaplayer.h:49
double mTolerance
The snapping tolerances for the layers, always in source coordinate systems of the layer...
Definition: qgssnapper.h:90
void append(const T &value)
iterator begin()
All results within the given layer tolerances are returned.
Definition: qgssnapper.h:82
double distance(const QgsGeometry &geom) const
Returns the minimum distance between this geometry and another geometry, using GEOS.
int length() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
const_iterator constEnd() const
QList< QgsMapLayer * > layers() const
return list of layers within map canvas.
QGis::GeometryType type() const
Returns type of the geometry as a QGis::GeometryType.
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QStringList readListEntry(const QString &scope, const QString &key, const QStringList &def=QStringList(), bool *ok=nullptr) const
Key value accessors.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
const QgsMapToPixel & mapToPixel() const
static double toleranceInMapUnits(double tolerance, QgsMapLayer *layer, const QgsMapSettings &mapSettings, UnitType units=LayerUnits)
Static function to translate tolerance value into layer units.
QgsTolerance::UnitType mUnitType
What unit is used for tolerance.
Definition: qgssnapper.h:94
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:105
int x() const
int y() const
QgsSnapper::SnappingType mSnapTo
What snapping type to use (snap to segment or to vertex)
Definition: qgssnapper.h:92
int size() const
QgsMapLayer * mapLayer(const QString &theLayerId)
Retrieve a pointer to a loaded layer by id.
int snapMapPoint(const QgsPoint &mapCoordPoint, QList< QgsSnappingResult > &snappingResult, const QList< QgsPoint > &excludePoints=QList< QgsPoint >())
Does the snapping operation.
Definition: qgssnapper.cpp:47
SnappingType
Snap to vertex, to segment or both.
Definition: qgssnapper.h:66
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=nullptr) const
int snapToBackgroundLayers(QPoint p, QList< QgsSnappingResult > &results, const QList< QgsPoint > &excludePoints=QList< QgsPoint >())
Snaps to the background layers.
QString number(int n, int base)
void append(const T &value)
A class that allows advanced snapping operations on a set of vector layers.
Definition: qgssnapper.h:62
QString id() const
Get this layer's unique ID, this ID is used to access this layer from map layer registry.
Several snapping results which have the same position are returned.
Definition: qgssnapper.h:80
A class to represent a point.
Definition: qgspoint.h:65
static QgsGeometry * fromPoint(const QgsPoint &point)
Creates a new geometry from a QgsPoint object.
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
int snapToCurrentLayer(QPoint p, QList< QgsSnappingResult > &results, QgsSnapper::SnappingType snap_to, double snappingTol=-1, const QList< QgsPoint > &excludePoints=QList< QgsPoint >(), bool allResutInTolerance=false)
Does a snap to the current layer.
QgsMapLayer * currentLayer()
returns current layer (set by legend widget)
QgsPoint toMapCoordinates(int x, int y) const
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
QgsGeometry * intersection(const QgsGeometry *geometry) const
Returns a geometry representing the points shared by this geometry and other.
Only one snapping result is returned.
Definition: qgssnapper.h:77
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QVariant value(const QString &key, const QVariant &defaultValue) const
const_iterator constBegin() const
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
Layer unit value.
Definition: qgstolerance.h:38
static QgsProject * instance()
access to canonical QgsProject instance
Definition: qgsproject.cpp:379
QgsVectorLayer * mLayer
The layer to which snapping is applied.
Definition: qgssnapper.h:88
int count(const T &value) const
static QgsGeometry * fromPolyline(const QgsPolyline &polyline)
Creates a new geometry from a QgsPolyline object.
void push_back(const T &value)
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:33
const_iterator constEnd() const
const_iterator constBegin() const
void setSnapLayers(const QList< QgsSnapper::SnapLayer > &snapLayers)
Definition: qgssnapper.cpp:146
QgsPoint asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
Represents a vector layer which manages a vector based data sets.
QString toString() const
iterator end()
void setMapCanvas(QgsMapCanvas *canvas)
void setSnapMode(QgsSnapper::SnappingMode snapMode)
Definition: qgssnapper.cpp:152