QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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
QString readEntry(const QString &scope, const QString &key, const QString &def=QString::null, bool *ok=nullptr) const
QgsPoint asPoint() const
Return contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
QList< QgsMapLayer * > layers() const
return list of layers within map canvas.
double distance(const QgsGeometry &geom) const
Returns the minimum distance between this geometry and another geometry, using GEOS.
double mTolerance
The snapping tolerances for the layers, always in source coordinate systems of the layer...
Definition: qgssnapper.h:91
void append(const T &value)
iterator begin()
All results within the given layer tolerances are returned.
Definition: qgssnapper.h:83
int length() const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
const_iterator constEnd() const
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:33
QgsPoint toMapCoordinates(int x, int y) const
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
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:95
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:109
int x() const
int y() const
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
QgsSnapper::SnappingType mSnapTo
What snapping type to use (snap to segment or to vertex)
Definition: qgssnapper.h:93
int size() const
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
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:67
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)
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
A class that allows advanced snapping operations on a set of vector layers.
Definition: qgssnapper.h:63
QStringList readListEntry(const QString &scope, const QString &key, const QStringList &def=QStringList(), bool *ok=nullptr) const
Key value accessors.
QGis::GeometryType type() const
Returns type of the geometry as a QGis::GeometryType.
Several snapping results which have the same position are returned.
Definition: qgssnapper.h:81
A class to represent a point.
Definition: qgspoint.h:117
const QgsMapToPixel & mapToPixel() const
static QgsGeometry * fromPoint(const QgsPoint &point)
Creates a new geometry from a QgsPoint object.
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)
Layer unit value.
Definition: qgstolerance.h:38
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
Only one snapping result is returned.
Definition: qgssnapper.h:78
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.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:382
QgsVectorLayer * mLayer
The layer to which snapping is applied.
Definition: qgssnapper.h:89
int count(const T &value) const
QgsGeometry * intersection(const QgsGeometry *geometry) const
Returns a geometry representing the points shared by this geometry and other.
static QgsGeometry * fromPolyline(const QgsPolyline &polyline)
Creates a new geometry from a QgsPolyline object.
void push_back(const T &value)
const_iterator constEnd() const
const_iterator constBegin() const
void setSnapLayers(const QList< QgsSnapper::SnapLayer > &snapLayers)
Definition: qgssnapper.cpp:146
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