QGIS API Documentation  2.99.0-Master (5753576)
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.isEmpty() && 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 }
Base class for all map layer types.
Definition: qgsmaplayer.h:50
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...
static QgsGeometry fromPolyline(const QgsPolyline &polyline)
Creates a new geometry from a QgsPolyline object.
QList< QgsMapLayer * > layers() const
return list of layers within map canvas.
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
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:97
All results within the given layer tolerances are returned.
Definition: qgssnapper.h:89
Both on vertices and segments.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:36
QgsPoint toMapCoordinates(int x, int y) const
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:79
QgsGeometry intersection(const QgsGeometry &geometry) const
Returns a geometry representing the points shared by this geometry and other.
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:74
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:99
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:33
SnappingType
Snap to vertex, to segment or both.
Definition: qgssnapper.h:72
int snapToBackgroundLayers(QPoint p, QList< QgsSnappingResult > &results, const QList< QgsPoint > &excludePoints=QList< QgsPoint >())
Snaps to the background layers.
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
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
bool isEmpty() const
Returns true if the geometry is empty (ie, contains no underlying geometry accessible via geometry)...
QStringList readListEntry(const QString &scope, const QString &key, const QStringList &def=QStringList(), bool *ok=nullptr) const
Key value accessors.
Several snapping results which have the same position are returned.
Definition: qgssnapper.h:87
A class to represent a point.
Definition: qgspoint.h:143
const QgsMapToPixel & mapToPixel() const
QgsWkbTypes::GeometryType type() const
Returns type of the geometry as a QgsWkbTypes::GeometryType.
bool topologicalEditing() const
Convenience function to query topological editing status.
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
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:83
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:350
QgsVectorLayer * mLayer
The layer to which snapping is applied.
Definition: qgssnapper.h:95
void setSnapLayers(const QList< QgsSnapper::SnapLayer > &snapLayers)
Definition: qgssnapper.cpp:132
Represents a vector layer which manages a vector based data sets.
void setMapCanvas(QgsMapCanvas *canvas)
void setSnapMode(QgsSnapper::SnappingMode snapMode)
Definition: qgssnapper.cpp:138