QGIS API Documentation  2.99.0-Master (08c2e66)
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 "qgsmaptopixel.h"
21 #include "qgsproject.h"
22 #include "qgsvectorlayer.h"
23 #include "qgstolerance.h"
24 #include <QSettings>
25 #include "qgslogger.h"
26 #include "qgsgeometry.h"
27 
29  : mMapCanvas( canvas )
30  , mSnapper( nullptr )
31 {
32  if ( !canvas )
33  return;
34 
35  mSnapper = new QgsSnapper( canvas->mapSettings() );
36 }
37 
38 QgsMapCanvasSnapper::QgsMapCanvasSnapper(): mMapCanvas( nullptr ), mSnapper( nullptr )
39 {
40 }
41 
43 {
44  delete mSnapper;
45 }
46 
48 {
49  mMapCanvas = canvas;
50  delete mSnapper;
51  if ( mMapCanvas )
52  {
53  mSnapper = new QgsSnapper( canvas->mapSettings() );
54  }
55  else
56  {
57  mSnapper = nullptr;
58  }
59 }
60 
61 int QgsMapCanvasSnapper::snapToCurrentLayer( QPoint p, QList<QgsSnappingResult>& results,
63  double snappingTol,
64  const QList<QgsPoint>& excludePoints,
65  bool allResutInTolerance )
66 {
67  results.clear();
68 
69  if ( !mSnapper || !mMapCanvas )
70  return 1;
71 
72  //topological editing on?
73  bool topologicalEditing = QgsProject::instance()->topologicalEditing();
74  if ( allResutInTolerance )
75  {
77  }
78  else if ( topologicalEditing == 0 )
79  {
81  }
82  else
83  {
85  }
86 
87  //current vector layer
88  QgsMapLayer* currentLayer = mMapCanvas->currentLayer();
89  if ( !currentLayer )
90  return 2;
91 
92  QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer *>( currentLayer );
93  if ( !vlayer )
94  return 3;
95 
96  QgsSnapper::SnapLayer snapLayer;
97  snapLayer.mLayer = vlayer;
98  snapLayer.mSnapTo = snap_to;
100 
101  if ( snappingTol < 0 )
102  {
103  //use search tolerance for vertex editing
104  snapLayer.mTolerance = QgsTolerance::vertexSearchRadius( vlayer, mMapCanvas->mapSettings() );
105  }
106  else
107  {
108  snapLayer.mTolerance = snappingTol;
109  }
110 
111  QList<QgsSnapper::SnapLayer> snapLayers;
112  snapLayers.append( snapLayer );
113  mSnapper->setSnapLayers( snapLayers );
114 
115  QgsPoint mapPoint = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( p );
116  if ( mSnapper->snapMapPoint( mapPoint, results, excludePoints ) != 0 )
117  return 4;
118 
119  return 0;
120 }
121 
122 int QgsMapCanvasSnapper::snapToBackgroundLayers( QPoint p, QList<QgsSnappingResult>& results, const QList<QgsPoint>& excludePoints )
123 {
124  const QgsPoint mapCoordPoint = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( p.x(), p.y() );
125  return snapToBackgroundLayers( mapCoordPoint, results, excludePoints );
126 }
127 
128 int QgsMapCanvasSnapper::snapToBackgroundLayers( const QgsPoint& point, QList<QgsSnappingResult>& results, const QList<QgsPoint>& excludePoints )
129 {
130  results.clear();
131 
132  if ( !mSnapper )
133  return 5;
134 
135  //topological editing on?
136  bool topologicalEditing = QgsProject::instance()->topologicalEditing();
137 
138  //snapping on intersection on?
139  int intersectionSnapping = QgsProject::instance()->readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/IntersectionSnapping" ), 0 );
140 
141  if ( topologicalEditing == 0 )
142  {
143  if ( intersectionSnapping == 0 )
145  else
147  }
148  else if ( intersectionSnapping == 0 )
149  {
151  }
152  else
153  {
155  }
156 
157  QgsVectorLayer* currentVectorLayer = dynamic_cast<QgsVectorLayer*>( mMapCanvas->currentLayer() );
158  if ( !currentVectorLayer )
159  {
160  return 1;
161  }
162 
163  //read snapping settings from project
164  QStringList layerIdList, enabledList, toleranceList, toleranceUnitList, snapToList;
165 
166  bool ok, snappingDefinedInProject;
167 
168  QSettings settings;
169  QString snappingMode = QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/SnappingMode" ), QStringLiteral( "current_layer" ), &snappingDefinedInProject );
170  QString defaultSnapToleranceUnit = snappingDefinedInProject ? QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapToleranceUnit" ) ) : settings.value( QStringLiteral( "/qgis/digitizing/default_snapping_tolerance_unit" ), "0" ).toString();
171  QString defaultSnapTolerance = snappingDefinedInProject ? QString::number( QgsProject::instance()->readDoubleEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapTolerance" ) ) ) : settings.value( QStringLiteral( "/qgis/digitizing/default_snapping_tolerance" ), "0" ).toString();
172  bool defaultSnapEnabled = settings.value( QStringLiteral( "/qgis/digitizing/default_snap_enabled" ), false ).toBool();
173 
174  QString defaultSnapType;
175  if ( snappingDefinedInProject )
176  {
177  defaultSnapType = QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapType" ) );
178  }
179  else
180  {
181  int dst;
182  dst = settings.value( QStringLiteral( "/qgis/digitizing/default_snap_mode" ), QgsSnappingConfig::Vertex ).toInt();
183  switch ( dst )
184  {
186  defaultSnapType = QStringLiteral( "to segment" );
187  break;
189  defaultSnapType = QStringLiteral( "to vertex and segment" );
190  break;
191  default:
192  defaultSnapType = QStringLiteral( "to vertex" );
193  break;
194  }
195  }
196 
197  if ( !snappingDefinedInProject && defaultSnapEnabled == false )
198  {
199  return 0;
200  }
201 
202  if ( snappingMode == QLatin1String( "current_layer" ) || !snappingDefinedInProject )
203  {
204  layerIdList.append( currentVectorLayer->id() );
205  enabledList.append( QStringLiteral( "enabled" ) );
206  toleranceList.append( defaultSnapTolerance );
207  toleranceUnitList.append( defaultSnapToleranceUnit );
208  snapToList.append( defaultSnapType );
209  }
210  else if ( snappingMode == QLatin1String( "all_layers" ) )
211  {
212  QList<QgsMapLayer*> allLayers = mMapCanvas->layers();
213  QList<QgsMapLayer*>::const_iterator layerIt = allLayers.constBegin();
214  for ( ; layerIt != allLayers.constEnd(); ++layerIt )
215  {
216  if ( !( *layerIt ) )
217  {
218  continue;
219  }
220  layerIdList.append(( *layerIt )->id() );
221  enabledList.append( QStringLiteral( "enabled" ) );
222  toleranceList.append( defaultSnapTolerance );
223  toleranceUnitList.append( defaultSnapToleranceUnit );
224  snapToList.append( defaultSnapType );
225  }
226  }
227  else //advanced
228  {
229  layerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingList" ), QStringList(), &ok );
230  enabledList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingEnabledList" ), QStringList(), &ok );
231  toleranceList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingToleranceList" ), QStringList(), &ok );
232  toleranceUnitList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingToleranceUnitList" ), QStringList(), &ok );
233  snapToList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnapToList" ), QStringList(), &ok );
234  }
235 
236  if ( !( layerIdList.size() == enabledList.size() &&
237  layerIdList.size() == toleranceList.size() &&
238  layerIdList.size() == toleranceUnitList.size() &&
239  layerIdList.size() == snapToList.size() ) )
240  {
241  // lists must have the same size, otherwise something is wrong
242  return 1;
243  }
244 
245  QList<QgsSnapper::SnapLayer> snapLayers;
246  QgsSnapper::SnapLayer snapLayer;
247 
248 
249 
250  // set layers, tolerances, snap to segment/vertex to QgsSnapper
251  QStringList::const_iterator layerIt( layerIdList.constBegin() );
252  QStringList::const_iterator tolIt( toleranceList.constBegin() );
253  QStringList::const_iterator tolUnitIt( toleranceUnitList.constBegin() );
254  QStringList::const_iterator snapIt( snapToList.constBegin() );
255  QStringList::const_iterator enabledIt( enabledList.constBegin() );
256  for ( ; layerIt != layerIdList.constEnd(); ++layerIt, ++tolIt, ++tolUnitIt, ++snapIt, ++enabledIt )
257  {
258  if ( *enabledIt != QLatin1String( "enabled" ) )
259  {
260  // skip layer if snapping is not enabled
261  continue;
262  }
263 
264  //layer
265  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( *layerIt ) );
266  if ( !vlayer || !vlayer->hasGeometryType() )
267  continue;
268 
269  snapLayer.mLayer = vlayer;
270 
271  //tolerance
272  snapLayer.mTolerance = tolIt->toDouble();
273  snapLayer.mUnitType = ( QgsTolerance::UnitType ) tolUnitIt->toInt();
274 
275  // segment or vertex
276  if ( *snapIt == QLatin1String( "to vertex" ) || *snapIt == QLatin1String( "to_vertex" ) )
277  {
278  snapLayer.mSnapTo = QgsSnapper::SnapToVertex;
279  }
280  else if ( *snapIt == QLatin1String( "to segment" ) || *snapIt == QLatin1String( "to_segment" ) )
281  {
283  }
284  else if ( *snapIt == QLatin1String( "to vertex and segment" ) || *snapIt == QLatin1String( "to_vertex_and_segment" ) )
285  {
287  }
288  else //off
289  {
290  continue;
291  }
292 
293  snapLayers.append( snapLayer );
294  }
295 
296  mSnapper->setSnapLayers( snapLayers );
297 
298  if ( mSnapper->snapMapPoint( point, results, excludePoints ) != 0 )
299  return 4;
300 
301  if ( intersectionSnapping != 1 )
302  return 0;
303 
304  QVector<QgsSnappingResult> segments;
305  QVector<QgsSnappingResult> points;
306  for ( QList<QgsSnappingResult>::const_iterator it = results.constBegin();
307  it != results.constEnd();
308  ++it )
309  {
310  if ( it->snappedVertexNr == -1 )
311  {
312  QgsDebugMsg( "segment" );
313  segments.push_back( *it );
314  }
315  else
316  {
317  QgsDebugMsg( "no segment" );
318  points.push_back( *it );
319  }
320  }
321 
322  if ( segments.count() < 2 )
323  return 0;
324 
325  QList<QgsSnappingResult> myResults;
326 
327  for ( QVector<QgsSnappingResult>::const_iterator oSegIt = segments.constBegin();
328  oSegIt != segments.constEnd();
329  ++oSegIt )
330  {
331  QgsDebugMsg( QString::number( oSegIt->beforeVertexNr ) );
332 
333  QVector<QgsPoint> vertexPoints;
334  vertexPoints.append( oSegIt->beforeVertex );
335  vertexPoints.append( oSegIt->afterVertex );
336 
337  QgsGeometry lineA = QgsGeometry::fromPolyline( vertexPoints );
338 
339  for ( QVector<QgsSnappingResult>::iterator iSegIt = segments.begin();
340  iSegIt != segments.end();
341  ++iSegIt )
342  {
343  QVector<QgsPoint> vertexPoints;
344  vertexPoints.append( iSegIt->beforeVertex );
345  vertexPoints.append( iSegIt->afterVertex );
346 
347  QgsGeometry lineB = QgsGeometry::fromPolyline( vertexPoints );
348  QgsGeometry intersectionPoint = lineA.intersection( lineB );
349 
350  if ( !intersectionPoint.isNull() && intersectionPoint.type() == QgsWkbTypes::PointGeometry )
351  {
352  //We have to check the intersection point is inside the tolerance distance for both layers
353  double toleranceA = 0;
354  double toleranceB = 0;
355  for ( int i = 0 ;i < snapLayers.size();++i )
356  {
357  if ( snapLayers[i].mLayer == oSegIt->layer )
358  {
359  toleranceA = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
360  }
361  if ( snapLayers[i].mLayer == iSegIt->layer )
362  {
363  toleranceB = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
364  }
365  }
366  QgsGeometry cursorPoint = QgsGeometry::fromPoint( point );
367  double distance = intersectionPoint.distance( cursorPoint );
368  if ( distance < toleranceA && distance < toleranceB )
369  {
370  iSegIt->snappedVertex = intersectionPoint.asPoint();
371  myResults.append( *iSegIt );
372  }
373  }
374  }
375  }
376 
377  if ( myResults.length() > 0 )
378  {
379  results.clear();
380  results = myResults;
381  }
382 
383  return 0;
384 }
bool topologicalEditing() const
Convenience function to query topological editing status.
Base class for all map layer types.
Definition: qgsmaplayer.h:52
static QgsGeometry fromPolyline(const QgsPolyline &polyline)
Creates a new geometry from a QgsPolyline object.
bool isNull() const
Returns true if the geometry is null (ie, contains no underlying geometry accessible via geometry)...
double mTolerance
The snapping tolerances for the layers, always in source coordinate systems of the layer...
Definition: qgssnapper.h:97
All results within the given layer tolerances are returned.
Definition: qgssnapper.h:89
Both on vertices and segments.
double distance(const QgsGeometry &geom) const
Returns the minimum distance between this geometry and another geometry, using GEOS.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
QList< QgsMapLayer * > layers() const
return list of layers within map canvas.
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:36
QgsGeometry intersection(const QgsGeometry &geometry) const
Returns a geometry representing the points shared by this geometry and other.
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:79
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:101
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:72
QgsSnapper::SnappingType mSnapTo
What snapping type to use (snap to segment or to vertex)
Definition: qgssnapper.h:99
int snapMapPoint(const QgsPoint &mapCoordPoint, QList< QgsSnappingResult > &snappingResult, const QList< QgsPoint > &excludePoints=QList< QgsPoint >())
Does the snapping operation.
Definition: qgssnapper.cpp:33
SnappingType
Snap to vertex, to segment or both.
Definition: qgssnapper.h:72
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.
static QgsGeometry fromPoint(const QgsPoint &point)
Creates a new geometry from a QgsPoint object.
A class that allows advanced snapping operations on a set of vector layers.
Definition: qgssnapper.h:68
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
Several snapping results which have the same position are returned.
Definition: qgssnapper.h:87
A class to represent a point.
Definition: qgspoint.h:143
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)
Layer unit value.
Definition: qgstolerance.h:39
QgsPoint toMapCoordinates(int x, int y) const
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
Only one snapping result is returned.
Definition: qgssnapper.h:83
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:355
QgsVectorLayer * mLayer
The layer to which snapping is applied.
Definition: qgssnapper.h:95
void setSnapLayers(const QList< QgsSnapper::SnapLayer > &snapLayers)
Definition: qgssnapper.cpp:132
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.
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
void setMapCanvas(QgsMapCanvas *canvas)
void setSnapMode(QgsSnapper::SnappingMode snapMode)
Definition: qgssnapper.cpp:138