QGIS API Documentation  2.99.0-Master (314842d)
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 "qgssettings.h"
25 #include "qgslogger.h"
26 #include "qgsgeometry.h"
27 
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 
62 int QgsMapCanvasSnapper::snapToCurrentLayer( QPoint p, QList<QgsSnappingResult> &results,
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  bool topologicalEditing = QgsProject::instance()->topologicalEditing();
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 
123 int QgsMapCanvasSnapper::snapToBackgroundLayers( QPoint p, QList<QgsSnappingResult> &results, const QList<QgsPoint> &excludePoints )
124 {
125  const QgsPoint mapCoordPoint = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( p.x(), p.y() );
126  return snapToBackgroundLayers( mapCoordPoint, results, excludePoints );
127 }
128 
129 int QgsMapCanvasSnapper::snapToBackgroundLayers( const QgsPoint &point, QList<QgsSnappingResult> &results, const QList<QgsPoint> &excludePoints )
130 {
131  results.clear();
132 
133  if ( !mSnapper )
134  return 5;
135 
136  //topological editing on?
137  bool topologicalEditing = QgsProject::instance()->topologicalEditing();
138 
139  //snapping on intersection on?
140  int intersectionSnapping = QgsProject::instance()->readNumEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/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  QgsSettings settings;
170  QString snappingMode = QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/SnappingMode" ), QStringLiteral( "current_layer" ), &snappingDefinedInProject );
171  QString defaultSnapToleranceUnit = snappingDefinedInProject ? QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapToleranceUnit" ) ) : settings.value( QStringLiteral( "qgis/digitizing/default_snapping_tolerance_unit" ), "0" ).toString();
172  QString defaultSnapTolerance = snappingDefinedInProject ? QString::number( QgsProject::instance()->readDoubleEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapTolerance" ) ) ) : settings.value( QStringLiteral( "qgis/digitizing/default_snapping_tolerance" ), "0" ).toString();
173  bool defaultSnapEnabled = settings.value( QStringLiteral( "qgis/digitizing/default_snap_enabled" ), false ).toBool();
174 
175  QString defaultSnapType;
176  if ( snappingDefinedInProject )
177  {
178  defaultSnapType = QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapType" ) );
179  }
180  else
181  {
182  int dst;
183  dst = settings.value( QStringLiteral( "qgis/digitizing/default_snap_mode" ), QgsSnappingConfig::Vertex ).toInt();
184  switch ( dst )
185  {
187  defaultSnapType = QStringLiteral( "to segment" );
188  break;
190  defaultSnapType = QStringLiteral( "to vertex and segment" );
191  break;
192  default:
193  defaultSnapType = QStringLiteral( "to vertex" );
194  break;
195  }
196  }
197 
198  if ( !snappingDefinedInProject && defaultSnapEnabled == false )
199  {
200  return 0;
201  }
202 
203  if ( snappingMode == QLatin1String( "current_layer" ) || !snappingDefinedInProject )
204  {
205  layerIdList.append( currentVectorLayer->id() );
206  enabledList.append( QStringLiteral( "enabled" ) );
207  toleranceList.append( defaultSnapTolerance );
208  toleranceUnitList.append( defaultSnapToleranceUnit );
209  snapToList.append( defaultSnapType );
210  }
211  else if ( snappingMode == QLatin1String( "all_layers" ) )
212  {
213  QList<QgsMapLayer *> allLayers = mMapCanvas->layers();
214  QList<QgsMapLayer *>::const_iterator layerIt = allLayers.constBegin();
215  for ( ; layerIt != allLayers.constEnd(); ++layerIt )
216  {
217  if ( !( *layerIt ) )
218  {
219  continue;
220  }
221  layerIdList.append( ( *layerIt )->id() );
222  enabledList.append( QStringLiteral( "enabled" ) );
223  toleranceList.append( defaultSnapTolerance );
224  toleranceUnitList.append( defaultSnapToleranceUnit );
225  snapToList.append( defaultSnapType );
226  }
227  }
228  else //advanced
229  {
230  layerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingList" ), QStringList(), &ok );
231  enabledList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingEnabledList" ), QStringList(), &ok );
232  toleranceList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingToleranceList" ), QStringList(), &ok );
233  toleranceUnitList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingToleranceUnitList" ), QStringList(), &ok );
234  snapToList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnapToList" ), QStringList(), &ok );
235  }
236 
237  if ( !( layerIdList.size() == enabledList.size() &&
238  layerIdList.size() == toleranceList.size() &&
239  layerIdList.size() == toleranceUnitList.size() &&
240  layerIdList.size() == snapToList.size() ) )
241  {
242  // lists must have the same size, otherwise something is wrong
243  return 1;
244  }
245 
246  QList<QgsSnapper::SnapLayer> snapLayers;
247  QgsSnapper::SnapLayer snapLayer;
248 
249 
250 
251  // set layers, tolerances, snap to segment/vertex to QgsSnapper
252  QStringList::const_iterator layerIt( layerIdList.constBegin() );
253  QStringList::const_iterator tolIt( toleranceList.constBegin() );
254  QStringList::const_iterator tolUnitIt( toleranceUnitList.constBegin() );
255  QStringList::const_iterator snapIt( snapToList.constBegin() );
256  QStringList::const_iterator enabledIt( enabledList.constBegin() );
257  for ( ; layerIt != layerIdList.constEnd(); ++layerIt, ++tolIt, ++tolUnitIt, ++snapIt, ++enabledIt )
258  {
259  if ( *enabledIt != QLatin1String( "enabled" ) )
260  {
261  // skip layer if snapping is not enabled
262  continue;
263  }
264 
265  //layer
266  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( *layerIt ) );
267  if ( !vlayer || !vlayer->hasGeometryType() )
268  continue;
269 
270  snapLayer.mLayer = vlayer;
271 
272  //tolerance
273  snapLayer.mTolerance = tolIt->toDouble();
274  snapLayer.mUnitType = ( QgsTolerance::UnitType ) tolUnitIt->toInt();
275 
276  // segment or vertex
277  if ( *snapIt == QLatin1String( "to vertex" ) || *snapIt == QLatin1String( "to_vertex" ) )
278  {
279  snapLayer.mSnapTo = QgsSnapper::SnapToVertex;
280  }
281  else if ( *snapIt == QLatin1String( "to segment" ) || *snapIt == QLatin1String( "to_segment" ) )
282  {
284  }
285  else if ( *snapIt == QLatin1String( "to vertex and segment" ) || *snapIt == QLatin1String( "to_vertex_and_segment" ) )
286  {
288  }
289  else //off
290  {
291  continue;
292  }
293 
294  snapLayers.append( snapLayer );
295  }
296 
297  mSnapper->setSnapLayers( snapLayers );
298 
299  if ( mSnapper->snapMapPoint( point, results, excludePoints ) != 0 )
300  return 4;
301 
302  if ( intersectionSnapping != 1 )
303  return 0;
304 
305  QVector<QgsSnappingResult> segments;
306  QVector<QgsSnappingResult> points;
307  for ( QList<QgsSnappingResult>::const_iterator it = results.constBegin();
308  it != results.constEnd();
309  ++it )
310  {
311  if ( it->snappedVertexNr == -1 )
312  {
313  QgsDebugMsg( "segment" );
314  segments.push_back( *it );
315  }
316  else
317  {
318  QgsDebugMsg( "no segment" );
319  points.push_back( *it );
320  }
321  }
322 
323  if ( segments.count() < 2 )
324  return 0;
325 
326  QList<QgsSnappingResult> myResults;
327 
328  for ( QVector<QgsSnappingResult>::const_iterator oSegIt = segments.constBegin();
329  oSegIt != segments.constEnd();
330  ++oSegIt )
331  {
332  QgsDebugMsg( QString::number( oSegIt->beforeVertexNr ) );
333 
334  QVector<QgsPoint> vertexPoints;
335  vertexPoints.append( oSegIt->beforeVertex );
336  vertexPoints.append( oSegIt->afterVertex );
337 
338  QgsGeometry lineA = QgsGeometry::fromPolyline( vertexPoints );
339 
340  for ( QVector<QgsSnappingResult>::iterator iSegIt = segments.begin();
341  iSegIt != segments.end();
342  ++iSegIt )
343  {
344  QVector<QgsPoint> vertexPoints;
345  vertexPoints.append( iSegIt->beforeVertex );
346  vertexPoints.append( iSegIt->afterVertex );
347 
348  QgsGeometry lineB = QgsGeometry::fromPolyline( vertexPoints );
349  QgsGeometry intersectionPoint = lineA.intersection( lineB );
350 
351  if ( !intersectionPoint.isNull() && intersectionPoint.type() == QgsWkbTypes::PointGeometry )
352  {
353  //We have to check the intersection point is inside the tolerance distance for both layers
354  double toleranceA = 0;
355  double toleranceB = 0;
356  for ( int i = 0 ; i < snapLayers.size(); ++i )
357  {
358  if ( snapLayers[i].mLayer == oSegIt->layer )
359  {
360  toleranceA = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
361  }
362  if ( snapLayers[i].mLayer == iSegIt->layer )
363  {
364  toleranceB = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
365  }
366  }
367  QgsGeometry cursorPoint = QgsGeometry::fromPoint( point );
368  double distance = intersectionPoint.distance( cursorPoint );
369  if ( distance < toleranceA && distance < toleranceB )
370  {
371  iSegIt->snappedVertex = intersectionPoint.asPoint();
372  myResults.append( *iSegIt );
373  }
374  }
375  }
376  }
377 
378  if ( myResults.length() > 0 )
379  {
380  results.clear();
381  results = myResults;
382  }
383 
384  return 0;
385 }
Base class for all map layer types.
Definition: qgsmaplayer.h:52
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 the list of layers shown within the map canvas.
bool isNull() const
Returns true if the geometry is null (ie, contains no underlying geometry accessible via geometry)...
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
This class is a composition of two QSettings instances:
Definition: qgssettings.h:51
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:72
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
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:37
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:359
QgsVectorLayer * mLayer
The layer to which snapping is applied.
Definition: qgssnapper.h:95
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=Section::NoSection) const
Returns the value for setting key.
QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
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