QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgssnappingutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssnappingutils.cpp
3  --------------------------------------
4  Date : November 2014
5  Copyright : (C) 2014 by Martin Dobias
6  Email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgssnappingutils.h"
17 
18 #include "qgsgeometry.h"
19 #include "qgsproject.h"
20 #include "qgsvectorlayer.h"
21 #include "qgslogger.h"
22 #include "qgsrenderer.h"
23 
24 QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
25  : QObject( parent )
26  , mSnappingConfig( QgsProject::instance() )
27  , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
28 {
29 }
30 
32 {
34 }
35 
36 
38 {
39  if ( !vl )
40  return nullptr;
41 
42  if ( !mLocators.contains( vl ) )
43  {
44  QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), nullptr );
45  connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
46  mLocators.insert( vl, vlpl );
47  }
48  return mLocators.value( vl );
49 }
50 
52 {
53  qDeleteAll( mLocators );
54  mLocators.clear();
55 
56  qDeleteAll( mTemporaryLocators );
57  mTemporaryLocators.clear();
58 }
59 
60 
61 QgsPointLocator *QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
62 {
63  if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
64  return nullptr;
65 
66  QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
67  pointMap.x() + tolerance, pointMap.y() + tolerance );
68 
69  QgsPointLocator *loc = locatorForLayer( vl );
70 
71  if ( loc->isIndexing() || isIndexPrepared( loc, aoi ) )
72  return loc;
73  else
74  return temporaryLocatorForLayer( vl, pointMap, tolerance );
75 }
76 
77 QgsPointLocator *QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
78 {
79  if ( mTemporaryLocators.contains( vl ) )
80  delete mTemporaryLocators.take( vl );
81 
82  QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
83  pointMap.x() + tolerance, pointMap.y() + tolerance );
84 
85  QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
86  connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
87 
88  mTemporaryLocators.insert( vl, vlpl );
89  return mTemporaryLocators.value( vl );
90 }
91 
92 bool QgsSnappingUtils::isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest )
93 {
94  if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
95  return true;
96 
97  if ( mStrategy == IndexExtent && loc->hasIndex() && ( !loc->extent() || loc->extent()->intersects( areaOfInterest ) ) )
98  return true;
99 
100  QgsRectangle aoi( areaOfInterest );
101  aoi.scale( 0.999 );
102  return mStrategy == IndexHybrid && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
103 }
104 
105 static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
106 {
107  if ( segments.isEmpty() )
108  return QgsPointLocator::Match();
109 
110  QSet<QgsPointXY> endpoints;
111 
112  // make a geometry
113  QVector<QgsGeometry> geoms;
114  const auto constSegments = segments;
115  for ( const QgsPointLocator::Match &m : constSegments )
116  {
117  if ( m.hasEdge() )
118  {
119  QgsPolylineXY pl( 2 );
120  m.edgePoints( pl[0], pl[1] );
121  geoms << QgsGeometry::fromPolylineXY( pl );
122  endpoints << pl[0] << pl[1];
123  }
124  }
125 
127 
128  // get intersection points
129  QList<QgsPointXY> newPoints;
130  if ( g.wkbType() == QgsWkbTypes::LineString )
131  {
132  const auto constAsPolyline = g.asPolyline();
133  for ( const QgsPointXY &p : constAsPolyline )
134  {
135  if ( !endpoints.contains( p ) )
136  newPoints << p;
137  }
138  }
140  {
141  const auto constAsMultiPolyline = g.asMultiPolyline();
142  for ( const QgsPolylineXY &pl : constAsMultiPolyline )
143  {
144  const auto constPl = pl;
145  for ( const QgsPointXY &p : constPl )
146  {
147  if ( !endpoints.contains( p ) )
148  newPoints << p;
149  }
150  }
151  }
152 
153  if ( newPoints.isEmpty() )
154  return QgsPointLocator::Match();
155 
156  // find the closest points
157  QgsPointXY minP;
158  double minSqrDist = 1e20; // "infinity"
159  const auto constNewPoints = newPoints;
160  for ( const QgsPointXY &p : constNewPoints )
161  {
162  double sqrDist = pt.sqrDist( p.x(), p.y() );
163  if ( sqrDist < minSqrDist )
164  {
165  minSqrDist = sqrDist;
166  minP = p;
167  }
168  }
169 
170  return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
171 }
172 
173 static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
174 {
175  // is candidate match relevant?
176  if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
177  return;
178 
179  // is candidate match actually better?
180  if ( bestMatch.isValid() && bestMatch.type() == candidateMatch.type() && bestMatch.distance() - 10e-6 < candidateMatch.distance() )
181  return;
182 
183  // ORDER
184  // Vertex, Intersection
185  // Middle
186  // Centroid
187  // Edge
188  // Area
189 
190  // First Vertex, or intersection
191  if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
192  return;
193  if ( candidateMatch.type() & QgsPointLocator::Vertex )
194  {
195  bestMatch = candidateMatch;
196  return;
197  }
198 
199  // prefer vertex, centroid, middle matches over edge matches (even if they are closer)
200  if ( ( bestMatch.type() & QgsPointLocator::Centroid || bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Edge || candidateMatch.type() & QgsPointLocator::Area ) )
201  return;
202 
203  // prefer middle matches over centroid matches (even if they are closer)
204  if ( ( bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Centroid ) )
205  return;
206 
207  bestMatch = candidateMatch; // the other match is better!
208 }
209 
210 static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
211 {
212  if ( type & QgsPointLocator::Vertex )
213  {
214  _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
215  }
216  if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
217  {
218  _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
219  }
220  if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
221  {
222  // if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
223  if ( type & QgsPointLocator::Edge )
224  tolerance = 0;
225  _replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
226  }
227  if ( type & QgsPointLocator::Centroid )
228  {
229  _replaceIfBetter( bestMatch, loc->nearestCentroid( pointMap, tolerance, filter ), tolerance );
230  }
232  {
233  _replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter ), tolerance );
234  }
235 }
236 
237 
238 static QgsPointLocator::Types _snappingTypeToPointLocatorType( QgsSnappingConfig::SnappingTypeFlag type )
239 {
240  return QgsPointLocator::Types( static_cast<int>( type ) );
241 }
242 
244 {
245  return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
246 }
247 
248 inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
249 {
250  return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
251  point.x() + tolerance, point.y() + tolerance );
252 }
253 
255 {
256  if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
257  {
258  return QgsPointLocator::Match();
259  }
260 
261  if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer )
262  {
263  if ( !mCurrentLayer || mSnappingConfig.typeFlag() == QgsSnappingConfig::NoSnapFlag )
264  return QgsPointLocator::Match();
265 
266  // data from project
267  double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
268  QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
269 
270  prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
271 
272  // use ad-hoc locator
273  QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
274  if ( !loc )
275  return QgsPointLocator::Match();
276 
277  QgsPointLocator::Match bestMatch;
278  QgsPointLocator::MatchList edges; // for snap on intersection
279  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
280 
281  if ( mSnappingConfig.intersectionSnapping() )
282  {
283  QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
284  if ( !locEdges )
285  return QgsPointLocator::Match();
286  edges = locEdges->edgesInRect( pointMap, tolerance );
287  }
288 
289  for ( QgsVectorLayer *vl : mExtraSnapLayers )
290  {
291  QgsPointLocator *loc = locatorForLayer( vl );
292  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
293  if ( mSnappingConfig.intersectionSnapping() )
294  edges << loc->edgesInRect( pointMap, tolerance );
295  }
296 
297  if ( mSnappingConfig.intersectionSnapping() )
298  {
299  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
300  }
301 
302  return bestMatch;
303  }
304  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AdvancedConfiguration )
305  {
306  QList<LayerAndAreaOfInterest> layers;
307  QList<LayerConfig> filteredConfigs;
308 
309  //maximum scale is the one with smallest denominator
310  //minimum scale is the one with highest denominator
311  //So : maxscale < range on which snapping is enabled < minscale
312  bool inRangeGlobal = ( mSnappingConfig.minimumScale() <= 0.0 || mMapSettings.scale() <= mSnappingConfig.minimumScale() )
313  && ( mSnappingConfig.maximumScale() <= 0.0 || mMapSettings.scale() >= mSnappingConfig.maximumScale() );
314 
315  for ( const LayerConfig &layerConfig : qgis::as_const( mLayers ) )
316  {
317  QgsSnappingConfig::IndividualLayerSettings layerSettings = mSnappingConfig.individualLayerSettings( layerConfig.layer );
318 
319  bool inRangeLayer = ( layerSettings.minimumScale() <= 0.0 || mMapSettings.scale() <= layerSettings.minimumScale() )
320  && ( layerSettings.maximumScale() <= 0.0 || mMapSettings.scale() >= layerSettings.maximumScale() );
321 
322  //If limit to scale is disabled, snapping activated on all layer
323  //If no per layer config is set use the global one, otherwise use the layer config
324  if ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Disabled
325  || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Global && inRangeGlobal )
326  || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::PerLayer && inRangeLayer ) )
327  {
328  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
329  layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
330  filteredConfigs << layerConfig;
331  }
332  }
333  prepareIndex( layers, relaxed );
334 
335  QgsPointLocator::Match bestMatch;
336  QgsPointLocator::MatchList edges; // for snap on intersection
337  double maxTolerance = 0;
339 
340  for ( const LayerConfig &layerConfig : qgis::as_const( filteredConfigs ) )
341  {
342  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
343  if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
344  {
345  _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
346 
347  if ( mSnappingConfig.intersectionSnapping() )
348  {
349  edges << loc->edgesInRect( pointMap, tolerance );
350  }
351  // We keep the maximum tolerance for intersection snapping and extra snapping
352  maxTolerance = std::max( maxTolerance, tolerance );
353  // To avoid yet an additional setting, on extra snappings, we use the combination of all enabled snap types
354  maxTypes = static_cast<QgsPointLocator::Type>( maxTypes | layerConfig.type );
355  }
356  }
357 
358  for ( QgsVectorLayer *vl : mExtraSnapLayers )
359  {
360  QgsPointLocator *loc = locatorForLayer( vl );
361  _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
362  if ( mSnappingConfig.intersectionSnapping() )
363  edges << loc->edgesInRect( pointMap, maxTolerance );
364  }
365 
366  if ( mSnappingConfig.intersectionSnapping() )
367  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
368 
369  return bestMatch;
370  }
371  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AllLayers )
372  {
373  // data from project
374  double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() );
375  QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
376  QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
377 
378  QList<LayerAndAreaOfInterest> layers;
379  const auto constLayers = mMapSettings.layers();
380  for ( QgsMapLayer *layer : constLayers )
381  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
382  layers << qMakePair( vl, aoi );
383  prepareIndex( layers, relaxed );
384 
385  QgsPointLocator::MatchList edges; // for snap on intersection
386  QgsPointLocator::Match bestMatch;
387 
388  for ( const LayerAndAreaOfInterest &entry : qgis::as_const( layers ) )
389  {
390  QgsVectorLayer *vl = entry.first;
391  if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
392  {
393  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
394 
395  if ( mSnappingConfig.intersectionSnapping() )
396  edges << loc->edgesInRect( pointMap, tolerance );
397  }
398  }
399 
400  for ( QgsVectorLayer *vl : mExtraSnapLayers )
401  {
402  QgsPointLocator *loc = locatorForLayer( vl );
403  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
404  if ( mSnappingConfig.intersectionSnapping() )
405  edges << loc->edgesInRect( pointMap, tolerance );
406  }
407 
408  if ( mSnappingConfig.intersectionSnapping() )
409  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
410 
411  return bestMatch;
412  }
413 
414  return QgsPointLocator::Match();
415 }
416 
417 void QgsSnappingUtils::onInitFinished( bool ok )
418 {
419  QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
420 
421  // point locator init didn't work out - too many features!
422  // let's make the allowed area smaller for the next time
423  if ( !ok )
424  {
425  mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
426  }
427 }
428 
429 void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed )
430 {
431  // check if we need to build any index
432  QList<LayerAndAreaOfInterest> layersToIndex;
433  const auto constLayers = layers;
434  for ( const LayerAndAreaOfInterest &entry : constLayers )
435  {
436  QgsVectorLayer *vl = entry.first;
437 
438  if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
439  continue;
440 
441  QgsPointLocator *loc = locatorForLayer( vl );
442 
443  if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
444  layersToIndex << entry;
445  }
446  if ( !layersToIndex.isEmpty() )
447  {
448  // build indexes
449  QElapsedTimer t;
450  int i = 0;
451 
452  if ( !relaxed )
453  {
454  t.start();
455  prepareIndexStarting( layersToIndex.count() );
456  }
457 
458  for ( const LayerAndAreaOfInterest &entry : layersToIndex )
459  {
460  QgsVectorLayer *vl = entry.first;
461  QgsPointLocator *loc = locatorForLayer( vl );
462 
463  if ( loc->isIndexing() && !relaxed )
464  {
466  }
467 
468 
469  if ( !mEnableSnappingForInvisibleFeature )
470  {
472  loc->setRenderContext( &ctx );
473  }
474 
475  if ( mStrategy == IndexExtent )
476  {
477  QgsRectangle rect( mMapSettings.visibleExtent() );
478  loc->setExtent( &rect );
479  loc->init( -1, relaxed );
480  }
481  else if ( mStrategy == IndexHybrid )
482  {
483  // first time the layer is used? - let's set an initial guess about indexing
484  if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
485  {
486  int totalFeatureCount = vl->featureCount();
487  if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
488  {
489  // index the whole layer
490  mHybridMaxAreaPerLayer[vl->id()] = -1;
491  }
492  else
493  {
494  // estimate for how big area it probably makes sense to build partial index to not exceed the limit
495  // (we may change the limit later)
496  QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
497  double totalArea = layerExtent.width() * layerExtent.height();
498  mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
499  }
500  }
501 
502  double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
503  if ( indexReasonableArea == -1 )
504  {
505  // we can safely index the whole layer
506  loc->init( -1, relaxed );
507  }
508  else
509  {
510  // use area as big as we think may fit into our limit
511  QgsPointXY c = entry.second.center();
512  double halfSide = std::sqrt( indexReasonableArea ) / 2;
513  QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
514  c.x() + halfSide, c.y() + halfSide );
515  loc->setExtent( &rect );
516 
517  // see if it's possible build index for this area
518  loc->init( mHybridPerLayerFeatureLimit, relaxed );
519  }
520 
521  }
522  else // full index strategy
523  loc->init( relaxed );
524 
525  if ( !relaxed )
526  prepareIndexProgress( ++i );
527  }
528 
529  if ( !relaxed )
530  {
531  QgsDebugMsg( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
532  }
533  }
534 }
535 
537 {
538  return mSnappingConfig;
539 }
540 
542 {
543  mEnableSnappingForInvisibleFeature = enable;
544 }
545 
547 {
548  if ( mSnappingConfig == config )
549  return;
550 
551  if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() )
552  onIndividualLayerSettingsChanged( config.individualLayerSettings() );
553 
554  mSnappingConfig = config;
555 
556  emit configChanged( mSnappingConfig );
557 }
558 
560 {
561  mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
562  emit configChanged( mSnappingConfig );
563 }
564 
566 {
567  if ( !mCurrentLayer )
568  return QgsPointLocator::Match();
569 
570  QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
571  double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
572 
573  QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
574  if ( !loc )
575  return QgsPointLocator::Match();
576 
577  QgsPointLocator::Match bestMatch;
578  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
579  return bestMatch;
580 }
581 
583 {
584  QString oldDestCRS = mMapSettings.destinationCrs().authid();
585  QString newDestCRS = settings.destinationCrs().authid();
586  mMapSettings = settings;
587 
588  if ( newDestCRS != oldDestCRS )
590 }
591 
593 {
594  mCurrentLayer = layer;
595 }
596 
598 {
599  QString msg = QStringLiteral( "--- SNAPPING UTILS DUMP ---\n" );
600 
601  if ( !mMapSettings.hasValidSettings() )
602  {
603  msg += QLatin1String( "invalid map settings!" );
604  return msg;
605  }
606 
607  QList<LayerConfig> layers;
608 
609  if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer )
610  {
611  if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer && !mCurrentLayer )
612  {
613  msg += QLatin1String( "no current layer!" );
614  return msg;
615  }
616 
617  layers << LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
618  }
619  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AllLayers )
620  {
621  const auto constLayers = mMapSettings.layers();
622  for ( QgsMapLayer *layer : constLayers )
623  {
624  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
625  layers << LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
626  }
627  }
628  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AdvancedConfiguration )
629  {
630  layers = mLayers;
631  }
632 
633  const auto constLayers = layers;
634  for ( const LayerConfig &layer : constLayers )
635  {
636  msg += QString( "layer : %1\n"
637  "config: %2 tolerance %3 %4\n" )
638  .arg( layer.layer->name() )
639  .arg( layer.type ).arg( layer.tolerance ).arg( layer.unit );
640 
641  if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid || mStrategy == IndexExtent )
642  {
643  if ( QgsPointLocator *loc = locatorForLayer( layer.layer ) )
644  {
645  QString extentStr, cachedGeoms, limit( QStringLiteral( "no max area" ) );
646  if ( const QgsRectangle *r = loc->extent() )
647  {
648  extentStr = QStringLiteral( " extent %1" ).arg( r->toString() );
649  }
650  else
651  extentStr = QStringLiteral( "full extent" );
652  if ( loc->hasIndex() )
653  cachedGeoms = QStringLiteral( "%1 feats" ).arg( loc->cachedGeometryCount() );
654  else
655  cachedGeoms = QStringLiteral( "not initialized" );
656  if ( mStrategy == IndexHybrid )
657  {
658  if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
659  {
660  double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
661  if ( maxArea != -1 )
662  limit = QStringLiteral( "max area %1" ).arg( maxArea );
663  }
664  else
665  limit = QStringLiteral( "not evaluated" );
666  }
667  msg += QStringLiteral( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
668  }
669  else
670  msg += QLatin1String( "index : ???\n" ); // should not happen
671  }
672  else
673  msg += QLatin1String( "index : NO\n" );
674  msg += QLatin1String( "-\n" );
675  }
676 
677  return msg;
678 }
679 
680 QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const
681 {
682  return mMapSettings.destinationCrs();
683 }
684 
685 void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
686 {
687  mLayers.clear();
688 
689  QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
690 
691  for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
692  {
693  if ( i->enabled() )
694  {
695  mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType( static_cast<QgsSnappingConfig::SnappingTypeFlag>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
696  }
697  }
698 }
QgsRectangle::height
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
QgsPointXY::y
double y
Definition: qgspointxy.h:48
QgsSnappingUtils::~QgsSnappingUtils
~QgsSnappingUtils() override
Definition: qgssnappingutils.cpp:31
QgsWkbTypes::NullGeometry
@ NullGeometry
Definition: qgswkbtypes.h:146
QgsRenderContext::fromMapSettings
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Definition: qgsrendercontext.cpp:197
QgsGeometry::fromPolylineXY
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
Definition: qgsgeometry.cpp:174
QgsSnappingConfig::Global
@ Global
Scale dependency using global min max range.
Definition: qgssnappingconfig.h:90
QgsSnappingUtils::IndexExtent
@ IndexExtent
For all layer build index of extent given in map settings.
Definition: qgssnappingutils.h:110
QgsPointLocator::isIndexing
bool isIndexing() const
Returns true if the point locator is currently indexing the data.
Definition: qgspointlocator.h:404
QgsPointLocator::setRenderContext
void setRenderContext(const QgsRenderContext *context)
Configure render context - if not nullptr, it will use to index only visible feature.
Definition: qgspointlocator.cpp:783
QgsPointXY::x
Q_GADGET double x
Definition: qgspointxy.h:47
QgsPointLocator::Invalid
@ Invalid
Invalid.
Definition: qgspointlocator.h:155
QgsPointLocator::nearestArea
Match nearestArea(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest area to the specified point - up to distance specified by tolerance Optional filter may ...
Definition: qgspointlocator.cpp:1230
QgsSnappingUtils::snapToCurrentLayer
QgsPointLocator::Match snapToCurrentLayer(QPoint point, QgsPointLocator::Types type, QgsPointLocator::MatchFilter *filter=nullptr)
Snap to current layer.
Definition: qgssnappingutils.cpp:565
QgsPolylineXY
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:51
QgsWkbTypes::LineString
@ LineString
Definition: qgswkbtypes.h:73
QgsSnappingUtils::config
QgsSnappingConfig config
Definition: qgssnappingutils.h:53
QgsMapSettings::hasValidSettings
bool hasValidSettings() const
Check whether the map settings are valid and can be used for rendering.
Definition: qgsmapsettings.cpp:366
QgsRenderContext
Contains information about the context of a rendering operation.
Definition: qgsrendercontext.h:58
QgsMapSettings::layerExtentToOutputExtent
QgsRectangle layerExtentToOutputExtent(const QgsMapLayer *layer, QgsRectangle extent) const
transform bounding box from layer's CRS to output CRS
Definition: qgsmapsettings.cpp:433
QgsPointLocator::cachedGeometryCount
int cachedGeometryCount() const
Returns how many geometries are cached in the index.
Definition: qgspointlocator.h:396
QgsSnappingUtils::setCurrentLayer
void setCurrentLayer(QgsVectorLayer *layer)
Sets current layer so that if mode is SnapCurrentLayer we know which layer to use.
Definition: qgssnappingutils.cpp:592
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsMapToPixel::toMapCoordinates
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
Definition: qgsmaptopixel.cpp:108
QgsVectorLayer::featureCount
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Definition: qgsvectorlayer.cpp:751
QgsPointLocator::edgesInRect
MatchList edgesInRect(const QgsRectangle &rect, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find edges within a specified rectangle Optional filter may discard unwanted matches.
Definition: qgspointlocator.cpp:1260
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsGeometry::asMultiPolyline
QgsMultiPolylineXY asMultiPolyline() const
Returns the contents of the geometry as a multi-linestring.
Definition: qgsgeometry.cpp:1664
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:95
QgsSnappingUtils::clearAllLocators
void clearAllLocators()
Deletes all existing locators (e.g. when destination CRS has changed and we need to reindex)
Definition: qgssnappingutils.cpp:51
QgsPointLocator::init
bool init(int maxFeaturesToIndex=-1, bool relaxed=false)
Prepare the index for queries.
Definition: qgspointlocator.cpp:828
QgsPointLocator::nearestVertex
Match nearestVertex(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest vertex to the specified point - up to distance specified by tolerance Optional filter ma...
Definition: qgspointlocator.cpp:1168
QgsPointXY::sqrDist
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:175
QgsSnappingUtils::IndexHybrid
@ IndexHybrid
For "big" layers using IndexNeverFull, for the rest IndexAlwaysFull. Compromise between speed and mem...
Definition: qgssnappingutils.h:109
QgsSnappingUtils::snapToMap
QgsPointLocator::Match snapToMap(QPoint point, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Snap to map according to the current configuration.
Definition: qgssnappingutils.cpp:243
QgsSnappingUtils::QgsSnappingUtils
QgsSnappingUtils(QObject *parent=nullptr, bool enableSnappingForInvisibleFeature=true)
Constructor for QgsSnappingUtils.
Definition: qgssnappingutils.cpp:24
QgsWkbTypes::MultiLineString
@ MultiLineString
Definition: qgswkbtypes.h:77
QgsSnappingConfig::ActiveLayer
@ ActiveLayer
On the active layer.
Definition: qgssnappingconfig.h:46
QgsSnappingConfig::minimumScale
double minimumScale() const
Returns the min scale (i.e.
Definition: qgssnappingconfig.cpp:672
QgsSnappingConfig::tolerance
double tolerance() const
Returns the tolerance.
Definition: qgssnappingconfig.cpp:310
QgsMapSettings::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context, which stores various information regarding which datum tran...
Definition: qgsmapsettings.cpp:401
QgsSnappingConfig::PerLayer
@ PerLayer
Scale dependency using min max range per layer.
Definition: qgssnappingconfig.h:91
QgsSnappingUtils::toggleEnabled
void toggleEnabled()
Toggles the state of snapping.
Definition: qgssnappingutils.cpp:559
QgsSnappingUtils::configChanged
void configChanged(const QgsSnappingConfig &snappingConfig)
Emitted when the snapping settings object changes.
QgsPointLocator::MatchList
class QList< QgsPointLocator::Match > MatchList
Definition: qgspointlocator.h:295
QgsSnappingUtils::dump
QString dump()
Gets extra information about the instance.
Definition: qgssnappingutils.cpp:597
QgsSnappingUtils::prepareIndexStarting
virtual void prepareIndexStarting(int count)
Called when starting to index with snapToMap - can be overridden and e.g. progress dialog can be prov...
Definition: qgssnappingutils.h:256
QgsRectangle::contains
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:342
_areaOfInterest
QgsRectangle _areaOfInterest(const QgsPointXY &point, double tolerance)
Definition: qgssnappingutils.cpp:248
QgsSnappingConfig::scaleDependencyMode
ScaleDependencyMode scaleDependencyMode() const
Returns the scale dependency mode.
Definition: qgssnappingconfig.cpp:697
QgsSnappingUtils::IndexNeverFull
@ IndexNeverFull
For all layers only create temporary indexes of small extent. Low memory usage, slower queries.
Definition: qgssnappingutils.h:108
QgsPointLocator::nearestMiddleOfSegment
Match nearestMiddleOfSegment(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest middle of segment to the specified point - up to distance specified by tolerance Optiona...
Definition: qgspointlocator.cpp:1197
QgsPointLocator::hasIndex
bool hasIndex() const
Indicate whether the data have been already indexed.
Definition: qgspointlocator.cpp:875
QgsCoordinateReferenceSystem::authid
QString authid() const
Returns the authority identifier for the CRS.
Definition: qgscoordinatereferencesystem.cpp:1321
QgsTolerance::vertexSearchRadius
static double vertexSearchRadius(const QgsMapSettings &mapSettings)
Static function to get vertex tolerance value.
Definition: qgstolerance.cpp:73
QgsPointLocator::Match::isValid
bool isValid() const
Definition: qgspointlocator.h:206
QgsPointLocator::setExtent
void setExtent(const QgsRectangle *extent)
Configure extent - if not nullptr, it will index only that area.
Definition: qgspointlocator.cpp:772
QgsPointLocator::Centroid
@ Centroid
Snapped to a centroid.
Definition: qgspointlocator.h:159
QgsMapLayer::id
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Definition: qgsmaplayer.cpp:148
QgsSnappingConfig::setEnabled
void setEnabled(bool enabled)
enables the snapping
Definition: qgssnappingconfig.cpp:245
QgsSnappingConfig
This is a container for configuration of the snapping of the project.
Definition: qgssnappingconfig.h:34
QgsSnappingConfig::AdvancedConfiguration
@ AdvancedConfiguration
On a per layer configuration basis.
Definition: qgssnappingconfig.h:48
QgsMapSettings::scale
double scale() const
Returns the calculated map scale.
Definition: qgsmapsettings.cpp:396
QgsPointLocator::extent
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if nullptr then it caches the whole layer.
Definition: qgspointlocator.h:136
qgsrenderer.h
QgsSnappingUtils::IndexAlwaysFull
@ IndexAlwaysFull
For all layers build index of full extent. Uses more memory, but queries are faster.
Definition: qgssnappingutils.h:107
QgsSnappingConfig::IndividualLayerSettings
This is a container of advanced configuration (per layer) of the snapping of the project.
Definition: qgssnappingconfig.h:122
QgsSnappingConfig::typeFlag
QgsSnappingConfig::SnappingTypeFlag typeFlag() const
Returns the flags type (vertices | segments | area | centroid | middle)
Definition: qgssnappingconfig.cpp:268
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:206
QgsPointLocator::Vertex
@ Vertex
Snapped to a vertex. Can be a vertex of the geometry or an intersection.
Definition: qgspointlocator.h:156
QgsVectorLayer::extent
QgsRectangle extent() const FINAL
Returns the extent of the layer.
Definition: qgsvectorlayer.cpp:833
QgsPointLocator::Match
Definition: qgspointlocator.h:185
qgsvectorlayer.h
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:44
QgsSnappingConfig::mode
SnappingMode mode() const
Returns the mode (all layers, active layer, per layer settings)
Definition: qgssnappingconfig.cpp:254
QgsMapSettings::destinationCrs
QgsCoordinateReferenceSystem destinationCrs() const
returns CRS of destination coordinate reference system
Definition: qgsmapsettings.cpp:318
QgsSnappingConfig::units
QgsTolerance::UnitType units() const
Returns the type of units.
Definition: qgssnappingconfig.cpp:324
QgsRectangle::intersects
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:328
QgsSnappingUtils::layers
QList< QgsSnappingUtils::LayerConfig > layers() const
Query layers used for snapping.
Definition: qgssnappingutils.h:168
qgsgeometry.h
QgsSnappingConfig::NoSnapFlag
@ NoSnapFlag
No snapping.
Definition: qgssnappingconfig.h:72
c
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
Definition: porting_processing.dox:1
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsPointLocator::Edge
@ Edge
Snapped to an edge.
Definition: qgspointlocator.h:157
QgsSnappingConfig::individualLayerSettings
QHash< QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings > individualLayerSettings() const
Returns individual snapping settings for all layers.
Definition: qgssnappingconfig.cpp:358
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:387
QgsRectangle::width
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
QgsMapLayer
Base class for all map layer types.
Definition: qgsmaplayer.h:83
QgsSnappingUtils::setEnableSnappingForInvisibleFeature
void setEnableSnappingForInvisibleFeature(bool enable)
Set if invisible features must be snapped or not.
Definition: qgssnappingutils.cpp:541
QgsPointLocator::Match::type
QgsPointLocator::Type type() const
Definition: qgspointlocator.h:204
QgsSnappingUtils::locatorForLayer
QgsPointLocator * locatorForLayer(QgsVectorLayer *vl)
Gets a point locator for the given layer.
Definition: qgssnappingutils.cpp:37
QgsSnappingConfig::intersectionSnapping
bool intersectionSnapping() const
Returns if the snapping on intersection is enabled.
Definition: qgssnappingconfig.cpp:338
QgsPointLocator
The class defines interface for querying point location:
Definition: qgspointlocator.h:100
QgsSnappingConfig::maximumScale
double maximumScale() const
Returns the max scale (i.e.
Definition: qgssnappingconfig.cpp:682
QgsSnappingConfig::IndividualLayerSettings::minimumScale
double minimumScale() const
Returns minimum scale on which snapping is limited.
Definition: qgssnappingconfig.cpp:124
QgsGeometry::asPolyline
QgsPolylineXY asPolyline() const
Returns the contents of the geometry as a polyline.
Definition: qgsgeometry.cpp:1559
QgsPointLocator::layer
QgsVectorLayer * layer() const
Gets associated layer.
Definition: qgspointlocator.h:124
QgsSnappingConfig::AllLayers
@ AllLayers
On all vector layers.
Definition: qgssnappingconfig.h:47
QgsPointLocator::nearestCentroid
Match nearestCentroid(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest centroid to the specified point - up to distance specified by tolerance Optional filter ...
Definition: qgspointlocator.cpp:1182
QgsPointLocator::MatchFilter
Interface that allows rejection of some matches in intersection queries (e.g.
Definition: qgspointlocator.h:306
QgsMapSettings::layers
QList< QgsMapLayer * > layers() const
Gets list of layers for map rendering The layers are stored in the reverse order of how they are rend...
Definition: qgsmapsettings.cpp:282
QgsSnappingUtils::setMapSettings
void setMapSettings(const QgsMapSettings &settings)
Assign current map settings to the utils - used for conversion between screen coords to map coords.
Definition: qgssnappingutils.cpp:582
QgsPointLocator::nearestEdge
Match nearestEdge(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest edge to the specified point - up to distance specified by tolerance Optional filter may ...
Definition: qgspointlocator.cpp:1212
QgsSnappingConfig::Disabled
@ Disabled
No scale dependency.
Definition: qgssnappingconfig.h:89
QgsPointLocator::MiddleOfSegment
@ MiddleOfSegment
Snapped to the middle of a segment.
Definition: qgspointlocator.h:160
qgslogger.h
QgsPointLocator::Type
Type
The type of a snap result or the filter type for a snap request.
Definition: qgspointlocator.h:154
QgsTolerance::toleranceInProjectUnits
static double toleranceInProjectUnits(double tolerance, QgsMapLayer *layer, const QgsMapSettings &mapSettings, QgsTolerance::UnitType units)
Static function to translate tolerance value into map units.
Definition: qgstolerance.cpp:39
QgsMapSettings
The QgsMapSettings class contains configuration for rendering of the map.
Definition: qgsmapsettings.h:88
QgsMapSettings::visibleExtent
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes takes output image size into accou...
Definition: qgsmapsettings.cpp:371
QgsSnappingConfig::IndividualLayerSettings::maximumScale
double maximumScale() const
Returns max scale on which snapping is limited.
Definition: qgssnappingconfig.cpp:134
QgsPointLocator::initFinished
void initFinished(bool ok)
Emitted whenever index has been built and initialization is finished.
QgsVectorLayer::geometryType
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
Definition: qgsvectorlayer.cpp:659
QgsSnappingUtils::LayerConfig
Configures how a certain layer should be handled in a snapping operation.
Definition: qgssnappingutils.h:122
QgsMapSettings::mapToPixel
const QgsMapToPixel & mapToPixel() const
Definition: qgsmapsettings.h:436
qgsproject.h
QgsPointLocator::waitForIndexingFinished
void waitForIndexingFinished()
If the point locator has been initialized relaxedly and is currently indexing, this methods waits for...
Definition: qgspointlocator.cpp:867
QgsGeometry::wkbType
QgsWkbTypes::Type wkbType() const SIP_HOLDGIL
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Definition: qgsgeometry.cpp:345
QgsSnappingUtils::setConfig
void setConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration controls the behavior of this object.
Definition: qgssnappingutils.cpp:546
QgsSnappingConfig::enabled
bool enabled() const
Returns if snapping is enabled.
Definition: qgssnappingconfig.cpp:240
QgsGeometry::unaryUnion
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries)
Compute the unary union on a list of geometries.
Definition: qgsgeometry.cpp:2762
qgssnappingutils.h
QgsPointLocator::Area
@ Area
Snapped to an area.
Definition: qgspointlocator.h:158
QgsPointLocator::Match::distance
double distance() const
for vertex / edge match units depending on what class returns it (geom.cache: layer units,...
Definition: qgspointlocator.h:222
QgsSnappingUtils::prepareIndexProgress
virtual void prepareIndexProgress(int index)
Called when finished indexing a layer with snapToMap. When index == count the indexing is complete.
Definition: qgssnappingutils.h:258