QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  // LineEndpoint
184  // Vertex, Intersection
185  // Middle
186  // Centroid
187  // Edge
188  // Area
189 
190  // first line endpoint -- these are like vertex matches, but even more strict
191  if ( ( bestMatch.type() & QgsPointLocator::LineEndpoint ) && !( candidateMatch.type() & QgsPointLocator::LineEndpoint ) )
192  return;
193  if ( candidateMatch.type() & QgsPointLocator::LineEndpoint )
194  {
195  bestMatch = candidateMatch;
196  return;
197  }
198 
199  // Second Vertex, or intersection
200  if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
201  return;
202  if ( candidateMatch.type() & QgsPointLocator::Vertex )
203  {
204  bestMatch = candidateMatch;
205  return;
206  }
207 
208  // prefer vertex, centroid, middle matches over edge matches (even if they are closer)
209  if ( ( bestMatch.type() & QgsPointLocator::Centroid || bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Edge || candidateMatch.type() & QgsPointLocator::Area ) )
210  return;
211 
212  // prefer middle matches over centroid matches (even if they are closer)
213  if ( ( bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Centroid ) )
214  return;
215 
216  bestMatch = candidateMatch; // the other match is better!
217 }
218 
219 static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
220 {
221  if ( type & QgsPointLocator::Vertex )
222  {
223  _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
224  }
225  if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
226  {
227  _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
228  }
229  if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
230  {
231  // if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
232  if ( type & QgsPointLocator::Edge )
233  tolerance = 0;
234  _replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
235  }
236  if ( type & QgsPointLocator::Centroid )
237  {
238  _replaceIfBetter( bestMatch, loc->nearestCentroid( pointMap, tolerance, filter ), tolerance );
239  }
241  {
242  _replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter ), tolerance );
243  }
244  if ( type & QgsPointLocator::LineEndpoint )
245  {
246  _replaceIfBetter( bestMatch, loc->nearestLineEndpoints( pointMap, tolerance, filter ), tolerance );
247  }
248 }
249 
250 
251 static QgsPointLocator::Types _snappingTypeToPointLocatorType( QgsSnappingConfig::SnappingTypeFlag type )
252 {
253  return QgsPointLocator::Types( static_cast<int>( type ) );
254 }
255 
257 {
258  return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
259 }
260 
261 inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
262 {
263  return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
264  point.x() + tolerance, point.y() + tolerance );
265 }
266 
268 {
269  if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
270  {
271  return QgsPointLocator::Match();
272  }
273 
274  if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer )
275  {
276  if ( !mCurrentLayer || mSnappingConfig.typeFlag() == QgsSnappingConfig::NoSnapFlag )
277  return QgsPointLocator::Match();
278 
279  // data from project
280  double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
281  QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
282 
283  prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
284 
285  // use ad-hoc locator
286  QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
287  if ( !loc )
288  return QgsPointLocator::Match();
289 
290  QgsPointLocator::Match bestMatch;
291  QgsPointLocator::MatchList edges; // for snap on intersection
292  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
293 
294  if ( mSnappingConfig.intersectionSnapping() )
295  {
296  QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
297  if ( !locEdges )
298  return QgsPointLocator::Match();
299  edges = locEdges->edgesInRect( pointMap, tolerance );
300  }
301 
302  for ( QgsVectorLayer *vl : mExtraSnapLayers )
303  {
304  QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
305  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
306  if ( mSnappingConfig.intersectionSnapping() )
307  edges << loc->edgesInRect( pointMap, tolerance );
308  }
309 
310  if ( mSnappingConfig.intersectionSnapping() )
311  {
312  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
313  }
314 
315  return bestMatch;
316  }
317  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AdvancedConfiguration )
318  {
319  QList<LayerAndAreaOfInterest> layers;
320  QList<LayerConfig> filteredConfigs;
321 
322  //maximum scale is the one with smallest denominator
323  //minimum scale is the one with highest denominator
324  //So : maxscale < range on which snapping is enabled < minscale
325  bool inRangeGlobal = ( mSnappingConfig.minimumScale() <= 0.0 || mMapSettings.scale() <= mSnappingConfig.minimumScale() )
326  && ( mSnappingConfig.maximumScale() <= 0.0 || mMapSettings.scale() >= mSnappingConfig.maximumScale() );
327 
328  for ( const LayerConfig &layerConfig : std::as_const( mLayers ) )
329  {
330  QgsSnappingConfig::IndividualLayerSettings layerSettings = mSnappingConfig.individualLayerSettings( layerConfig.layer );
331 
332  bool inRangeLayer = ( layerSettings.minimumScale() <= 0.0 || mMapSettings.scale() <= layerSettings.minimumScale() )
333  && ( layerSettings.maximumScale() <= 0.0 || mMapSettings.scale() >= layerSettings.maximumScale() );
334 
335  //If limit to scale is disabled, snapping activated on all layer
336  //If no per layer config is set use the global one, otherwise use the layer config
337  if ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Disabled
338  || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Global && inRangeGlobal )
339  || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::PerLayer && inRangeLayer ) )
340  {
341  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
342  layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
343  filteredConfigs << layerConfig;
344  }
345  }
346  prepareIndex( layers, relaxed );
347 
348  QgsPointLocator::Match bestMatch;
349  QgsPointLocator::MatchList edges; // for snap on intersection
350  double maxTolerance = 0;
352 
353  for ( const LayerConfig &layerConfig : std::as_const( filteredConfigs ) )
354  {
355  double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
356  if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
357  {
358  _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
359 
360  if ( mSnappingConfig.intersectionSnapping() )
361  {
362  edges << loc->edgesInRect( pointMap, tolerance );
363  }
364  // We keep the maximum tolerance for intersection snapping and extra snapping
365  maxTolerance = std::max( maxTolerance, tolerance );
366  // To avoid yet an additional setting, on extra snappings, we use the combination of all enabled snap types
367  maxTypes = static_cast<QgsPointLocator::Type>( maxTypes | layerConfig.type );
368  }
369  }
370 
371  for ( QgsVectorLayer *vl : mExtraSnapLayers )
372  {
373  QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, maxTolerance );
374  _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
375  if ( mSnappingConfig.intersectionSnapping() )
376  edges << loc->edgesInRect( pointMap, maxTolerance );
377  }
378 
379  if ( mSnappingConfig.intersectionSnapping() )
380  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
381 
382  return bestMatch;
383  }
384  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AllLayers )
385  {
386  // data from project
387  double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() );
388  QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
389  QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
390 
391  QList<LayerAndAreaOfInterest> layers;
392  const auto constLayers = mMapSettings.layers();
393  for ( QgsMapLayer *layer : constLayers )
394  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
395  layers << qMakePair( vl, aoi );
396  prepareIndex( layers, relaxed );
397 
398  QgsPointLocator::MatchList edges; // for snap on intersection
399  QgsPointLocator::Match bestMatch;
400 
401  for ( const LayerAndAreaOfInterest &entry : std::as_const( layers ) )
402  {
403  QgsVectorLayer *vl = entry.first;
404  if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
405  {
406  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
407 
408  if ( mSnappingConfig.intersectionSnapping() )
409  edges << loc->edgesInRect( pointMap, tolerance );
410  }
411  }
412 
413  for ( QgsVectorLayer *vl : mExtraSnapLayers )
414  {
415  QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
416  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
417  if ( mSnappingConfig.intersectionSnapping() )
418  edges << loc->edgesInRect( pointMap, tolerance );
419  }
420 
421  if ( mSnappingConfig.intersectionSnapping() )
422  _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
423 
424  return bestMatch;
425  }
426 
427  return QgsPointLocator::Match();
428 }
429 
430 void QgsSnappingUtils::onInitFinished( bool ok )
431 {
432  QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
433 
434  // point locator init didn't work out - too many features!
435  // let's make the allowed area smaller for the next time
436  if ( !ok )
437  {
438  mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
439  }
440 }
441 
442 void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed )
443 {
444  // check if we need to build any index
445  QList<LayerAndAreaOfInterest> layersToIndex;
446  const auto constLayers = layers;
447  for ( const LayerAndAreaOfInterest &entry : constLayers )
448  {
449  QgsVectorLayer *vl = entry.first;
450 
451  if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
452  continue;
453 
454  QgsPointLocator *loc = locatorForLayer( vl );
455 
456  if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
457  layersToIndex << entry;
458  }
459  if ( !layersToIndex.isEmpty() )
460  {
461  // build indexes
462  QElapsedTimer t;
463  int i = 0;
464 
465  if ( !relaxed )
466  {
467  t.start();
468  prepareIndexStarting( layersToIndex.count() );
469  }
470 
471  for ( const LayerAndAreaOfInterest &entry : layersToIndex )
472  {
473  QgsVectorLayer *vl = entry.first;
474  QgsPointLocator *loc = locatorForLayer( vl );
475 
476  if ( loc->isIndexing() && !relaxed )
477  {
479  }
480 
481 
482  if ( !mEnableSnappingForInvisibleFeature )
483  {
485  loc->setRenderContext( &ctx );
486  }
487 
488  if ( mStrategy == IndexExtent )
489  {
490  QgsRectangle rect( mMapSettings.visibleExtent() );
491  loc->setExtent( &rect );
492  loc->init( -1, relaxed );
493  }
494  else if ( mStrategy == IndexHybrid )
495  {
496  // first time the layer is used? - let's set an initial guess about indexing
497  if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
498  {
499  long long totalFeatureCount = vl->featureCount();
500  if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
501  {
502  // index the whole layer
503  mHybridMaxAreaPerLayer[vl->id()] = -1;
504  }
505  else
506  {
507  // estimate for how big area it probably makes sense to build partial index to not exceed the limit
508  // (we may change the limit later)
509  QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
510  double totalArea = layerExtent.width() * layerExtent.height();
511  mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
512  }
513  }
514 
515  double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
516  if ( indexReasonableArea == -1 )
517  {
518  // we can safely index the whole layer
519  loc->init( -1, relaxed );
520  }
521  else
522  {
523  // use area as big as we think may fit into our limit
524  QgsPointXY c = entry.second.center();
525  double halfSide = std::sqrt( indexReasonableArea ) / 2;
526  QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
527  c.x() + halfSide, c.y() + halfSide );
528  loc->setExtent( &rect );
529 
530  // see if it's possible build index for this area
531  loc->init( mHybridPerLayerFeatureLimit, relaxed );
532  }
533 
534  }
535  else // full index strategy
536  loc->init( relaxed );
537 
538  if ( !relaxed )
539  prepareIndexProgress( ++i );
540  }
541 
542  if ( !relaxed )
543  {
544  QgsDebugMsg( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ) );
545  }
546  }
547 }
548 
550 {
551  return mSnappingConfig;
552 }
553 
555 {
556  mEnableSnappingForInvisibleFeature = enable;
557 }
558 
560 {
561  if ( mSnappingConfig == config )
562  return;
563 
564  if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() )
565  onIndividualLayerSettingsChanged( config.individualLayerSettings() );
566 
567  mSnappingConfig = config;
568 
569  emit configChanged( mSnappingConfig );
570 }
571 
573 {
574  mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
575  emit configChanged( mSnappingConfig );
576 }
577 
579 {
580  if ( !mCurrentLayer )
581  return QgsPointLocator::Match();
582 
583  QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
584  double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
585 
586  QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
587  if ( !loc )
588  return QgsPointLocator::Match();
589 
590  QgsPointLocator::Match bestMatch;
591  _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
592  return bestMatch;
593 }
594 
596 {
597  QString oldDestCRS = mMapSettings.destinationCrs().authid();
598  QString newDestCRS = settings.destinationCrs().authid();
599  mMapSettings = settings;
600 
601  if ( newDestCRS != oldDestCRS )
603 }
604 
606 {
607  mCurrentLayer = layer;
608 }
609 
611 {
612  QString msg = QStringLiteral( "--- SNAPPING UTILS DUMP ---\n" );
613 
614  if ( !mMapSettings.hasValidSettings() )
615  {
616  msg += QLatin1String( "invalid map settings!" );
617  return msg;
618  }
619 
620  QList<LayerConfig> layers;
621 
622  if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer )
623  {
624  if ( mSnappingConfig.mode() == QgsSnappingConfig::ActiveLayer && !mCurrentLayer )
625  {
626  msg += QLatin1String( "no current layer!" );
627  return msg;
628  }
629 
630  layers << LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
631  }
632  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AllLayers )
633  {
634  const auto constLayers = mMapSettings.layers();
635  for ( QgsMapLayer *layer : constLayers )
636  {
637  if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
638  layers << LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
639  }
640  }
641  else if ( mSnappingConfig.mode() == QgsSnappingConfig::AdvancedConfiguration )
642  {
643  layers = mLayers;
644  }
645 
646  const auto constLayers = layers;
647  for ( const LayerConfig &layer : constLayers )
648  {
649  msg += QString( "layer : %1\n"
650  "config: %2 tolerance %3 %4\n" )
651  .arg( layer.layer->name() )
652  .arg( layer.type ).arg( layer.tolerance ).arg( layer.unit );
653 
654  if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid || mStrategy == IndexExtent )
655  {
656  if ( QgsPointLocator *loc = locatorForLayer( layer.layer ) )
657  {
658  QString extentStr, cachedGeoms, limit( QStringLiteral( "no max area" ) );
659  if ( const QgsRectangle *r = loc->extent() )
660  {
661  extentStr = QStringLiteral( " extent %1" ).arg( r->toString() );
662  }
663  else
664  extentStr = QStringLiteral( "full extent" );
665  if ( loc->hasIndex() )
666  cachedGeoms = QStringLiteral( "%1 feats" ).arg( loc->cachedGeometryCount() );
667  else
668  cachedGeoms = QStringLiteral( "not initialized" );
669  if ( mStrategy == IndexHybrid )
670  {
671  if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
672  {
673  double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
674  if ( maxArea != -1 )
675  limit = QStringLiteral( "max area %1" ).arg( maxArea );
676  }
677  else
678  limit = QStringLiteral( "not evaluated" );
679  }
680  msg += QStringLiteral( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
681  }
682  else
683  msg += QLatin1String( "index : ???\n" ); // should not happen
684  }
685  else
686  msg += QLatin1String( "index : NO\n" );
687  msg += QLatin1String( "-\n" );
688  }
689 
690  return msg;
691 }
692 
693 QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const
694 {
695  return mMapSettings.destinationCrs();
696 }
697 
698 void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
699 {
700  mLayers.clear();
701 
702  QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
703 
704  for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
705  {
706  if ( i->enabled() )
707  {
708  mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType( static_cast<QgsSnappingConfig::SnappingTypeFlag>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
709  }
710  }
711 }
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:70
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.
@ LineEndpoint
Start or end points of lines only (since QGIS 3.20)
Match nearestLineEndpoints(const QgsPointXY &point, double tolerance, QgsPointLocator::MatchFilter *filter=nullptr, bool relaxed=false)
Find nearest line endpoint (start or end vertex) to the specified point - up to distance specified by...
A class to represent a 2D point.
Definition: qgspointxy.h:59
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:190
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
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 intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:230
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
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.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsRectangle extent() const FINAL
Returns the extent of the layer.
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.