QGIS API Documentation  2.99.0-Master (6a61179)
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 
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  QSettings 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 defaultSnapType = snappingDefinedInProject ? QgsProject::instance()->readEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapType" ) ) : settings.value( QStringLiteral( "/qgis/digitizing/default_snap_mode" ), "off" ).toString();
173  QString defaultSnapTolerance = snappingDefinedInProject ? QString::number( QgsProject::instance()->readDoubleEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/DefaultSnapTolerance" ) ) ) : settings.value( QStringLiteral( "/qgis/digitizing/default_snapping_tolerance" ), "0" ).toString();
174 
175  if ( !snappingDefinedInProject && defaultSnapType == QLatin1String( "off" ) )
176  {
177  return 0;
178  }
179 
180  if ( snappingMode == QLatin1String( "current_layer" ) || !snappingDefinedInProject )
181  {
182  layerIdList.append( currentVectorLayer->id() );
183  enabledList.append( QStringLiteral( "enabled" ) );
184  toleranceList.append( defaultSnapTolerance );
185  toleranceUnitList.append( defaultSnapToleranceUnit );
186  snapToList.append( defaultSnapType );
187  }
188  else if ( snappingMode == QLatin1String( "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( QStringLiteral( "enabled" ) );
200  toleranceList.append( defaultSnapTolerance );
201  toleranceUnitList.append( defaultSnapToleranceUnit );
202  snapToList.append( defaultSnapType );
203  }
204  }
205  else //advanced
206  {
207  layerIdList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingList" ), QStringList(), &ok );
208  enabledList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingEnabledList" ), QStringList(), &ok );
209  toleranceList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingToleranceList" ), QStringList(), &ok );
210  toleranceUnitList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/LayerSnappingToleranceUnitList" ), QStringList(), &ok );
211  snapToList = QgsProject::instance()->readListEntry( QStringLiteral( "Digitizing" ), QStringLiteral( "/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 != QLatin1String( "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 == QLatin1String( "to vertex" ) || *snapIt == QLatin1String( "to_vertex" ) )
255  {
256  snapLayer.mSnapTo = QgsSnapper::SnapToVertex;
257  }
258  else if ( *snapIt == QLatin1String( "to segment" ) || *snapIt == QLatin1String( "to_segment" ) )
259  {
261  }
262  else if ( *snapIt == QLatin1String( "to vertex and segment" ) || *snapIt == QLatin1String( "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 
282  QVector<QgsSnappingResult> segments;
283  QVector<QgsSnappingResult> points;
284  for ( QList<QgsSnappingResult>::const_iterator it = results.constBegin();
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 
328  if ( !intersectionPoint.isEmpty() && intersectionPoint.type() == QgsWkbTypes::PointGeometry )
329  {
330  //We have to check the intersection point is inside the tolerance distance for both layers
331  double toleranceA = 0;
332  double toleranceB = 0;
333  for ( int i = 0 ;i < snapLayers.size();++i )
334  {
335  if ( snapLayers[i].mLayer == oSegIt->layer )
336  {
337  toleranceA = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
338  }
339  if ( snapLayers[i].mLayer == iSegIt->layer )
340  {
341  toleranceB = QgsTolerance::toleranceInMapUnits( snapLayers[i].mTolerance, snapLayers[i].mLayer, mMapCanvas->mapSettings(), snapLayers[i].mUnitType );
342  }
343  }
344  QgsGeometry cursorPoint = QgsGeometry::fromPoint( point );
345  double distance = intersectionPoint.distance( cursorPoint );
346  if ( distance < toleranceA && distance < toleranceB )
347  {
348  iSegIt->snappedVertex = intersectionPoint.asPoint();
349  myResults.append( *iSegIt );
350  }
351  }
352  }
353  }
354 
355  if ( myResults.length() > 0 )
356  {
357  results.clear();
358  results = myResults;
359  }
360 
361  return 0;
362 }
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...
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.
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:96
All results within the given layer tolerances are returned.
Definition: qgssnapper.h:88
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
UnitType
Type of unit of tolerance value from settings.
Definition: qgstolerance.h:35
QgsPoint toMapCoordinates(int x, int y) const
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:78
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:100
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:106
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:98
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:71
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 QgsMapLayerRegistry.
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:67
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:86
A class to represent a point.
Definition: qgspoint.h:111
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: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:82
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:348
QgsVectorLayer * mLayer
The layer to which snapping is applied.
Definition: qgssnapper.h:94
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