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