QGIS API Documentation 3.41.0-Master (88383c3d16f)
Loading...
Searching...
No Matches
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 "moc_qgssnappingutils.cpp"
18#include "qgsgeometry.h"
19#include "qgsproject.h"
20#include "qgsvectorlayer.h"
21#include "qgslogger.h"
22#include "qgsrendercontext.h"
23
24QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
25 : QObject( parent )
26 , mSnappingConfig( QgsProject::instance() ) // skip-keyword-check
27 , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
28{
29}
30
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 connect( vl, &QObject::destroyed, this, [this, vl]()
47 {
48 delete mLocators.take( vl );
49 } );
50
51 mLocators.insert( vl, vlpl );
52 }
53 return mLocators.value( vl );
54}
55
57{
58 qDeleteAll( mLocators );
59 mLocators.clear();
60
61 qDeleteAll( mTemporaryLocators );
62 mTemporaryLocators.clear();
63}
64
65
66QgsPointLocator *QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
67{
68 if ( vl->geometryType() == Qgis::GeometryType::Null || mStrategy == IndexNeverFull )
69 return nullptr;
70
71 QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
72 pointMap.x() + tolerance, pointMap.y() + tolerance );
73
75
76 if ( loc->isIndexing() || isIndexPrepared( loc, aoi ) )
77 return loc;
78 else
79 return temporaryLocatorForLayer( vl, pointMap, tolerance );
80}
81
82QgsPointLocator *QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
83{
84 if ( mTemporaryLocators.contains( vl ) )
85 delete mTemporaryLocators.take( vl );
86
87 QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
88 pointMap.x() + tolerance, pointMap.y() + tolerance );
89
90 QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
91 connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
92
93 mTemporaryLocators.insert( vl, vlpl );
94 return mTemporaryLocators.value( vl );
95}
96
97bool QgsSnappingUtils::isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest )
98{
99 if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
100 return true;
101
102 if ( mStrategy == IndexExtent && loc->hasIndex() && ( !loc->extent() || loc->extent()->intersects( areaOfInterest ) ) )
103 return true;
104
105 QgsRectangle aoi( areaOfInterest );
106 aoi.scale( 0.999 );
107 return mStrategy == IndexHybrid && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
108}
109
110static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
111{
112 if ( segments.isEmpty() )
113 return QgsPointLocator::Match();
114
115 QSet<QgsPointXY> endpoints;
116
117 // make a geometry
118 QVector<QgsGeometry> geoms;
119 const auto constSegments = segments;
120 for ( const QgsPointLocator::Match &m : constSegments )
121 {
122 if ( m.hasEdge() )
123 {
124 QgsPolylineXY pl( 2 );
125 m.edgePoints( pl[0], pl[1] );
126 geoms << QgsGeometry::fromPolylineXY( pl );
127 endpoints << pl[0] << pl[1];
128 }
129 }
130
132
133 // get intersection points
134 QList<QgsPointXY> newPoints;
136 {
137 const auto constAsPolyline = g.asPolyline();
138 for ( const QgsPointXY &p : constAsPolyline )
139 {
140 if ( !endpoints.contains( p ) )
141 newPoints << p;
142 }
143 }
145 {
146 const auto constAsMultiPolyline = g.asMultiPolyline();
147 for ( const QgsPolylineXY &pl : constAsMultiPolyline )
148 {
149 const auto constPl = pl;
150 for ( const QgsPointXY &p : constPl )
151 {
152 if ( !endpoints.contains( p ) )
153 newPoints << p;
154 }
155 }
156 }
157
158 if ( newPoints.isEmpty() )
159 return QgsPointLocator::Match();
160
161 // find the closest points
162 QgsPointXY minP;
163 double minSqrDist = 1e20; // "infinity"
164 const auto constNewPoints = newPoints;
165 for ( const QgsPointXY &p : constNewPoints )
166 {
167 double sqrDist = pt.sqrDist( p.x(), p.y() );
168 if ( sqrDist < minSqrDist )
169 {
170 minSqrDist = sqrDist;
171 minP = p;
172 }
173 }
174
175 return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
176}
177
178static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
179{
180 // is candidate match relevant?
181 if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
182 return;
183
184 // is candidate match actually better?
185 if ( bestMatch.isValid() && bestMatch.type() == candidateMatch.type() && bestMatch.distance() - 10e-6 < candidateMatch.distance() )
186 return;
187
188 // ORDER
189 // LineEndpoint
190 // Vertex, Intersection
191 // Middle
192 // Centroid
193 // Edge
194 // Area
195
196 // first line endpoint -- these are like vertex matches, but even more strict
197 if ( ( bestMatch.type() & QgsPointLocator::LineEndpoint ) && !( candidateMatch.type() & QgsPointLocator::LineEndpoint ) )
198 return;
199 if ( candidateMatch.type() & QgsPointLocator::LineEndpoint )
200 {
201 bestMatch = candidateMatch;
202 return;
203 }
204
205 // Second Vertex, or intersection
206 if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
207 return;
208 if ( candidateMatch.type() & QgsPointLocator::Vertex )
209 {
210 bestMatch = candidateMatch;
211 return;
212 }
213
214 // prefer vertex, centroid, middle matches over edge matches (even if they are closer)
215 if ( ( bestMatch.type() & QgsPointLocator::Centroid || bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Edge || candidateMatch.type() & QgsPointLocator::Area ) )
216 return;
217
218 // prefer middle matches over centroid matches (even if they are closer)
219 if ( ( bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Centroid ) )
220 return;
221
222 bestMatch = candidateMatch; // the other match is better!
223}
224
225static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
226{
227 if ( type & QgsPointLocator::Vertex )
228 {
229 _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
230 }
231 if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
232 {
233 _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
234 }
235 if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
236 {
237 // if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
238 if ( type & QgsPointLocator::Edge )
239 tolerance = 0;
240 _replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
241 }
242 if ( type & QgsPointLocator::Centroid )
243 {
244 _replaceIfBetter( bestMatch, loc->nearestCentroid( pointMap, tolerance, filter, relaxed ), tolerance );
245 }
247 {
248 _replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter, relaxed ), tolerance );
249 }
251 {
252 _replaceIfBetter( bestMatch, loc->nearestLineEndpoints( pointMap, tolerance, filter, relaxed ), tolerance );
253 }
254}
255
256
257static QgsPointLocator::Types _snappingTypeToPointLocatorType( Qgis::SnappingTypes type )
258{
259 return QgsPointLocator::Types( static_cast<int>( type ) );
260}
261
263{
264 return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
265}
266
267inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
268{
269 return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
270 point.x() + tolerance, point.y() + tolerance );
271}
272
274{
275 if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
276 {
277 return QgsPointLocator::Match();
278 }
279
280 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
281 {
282 if ( !mCurrentLayer || mSnappingConfig.typeFlag().testFlag( Qgis::SnappingType::NoSnap ) )
283 return QgsPointLocator::Match();
284
285 // data from project
286 double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
287 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
288
289 prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
290
291 // use ad-hoc locator
292 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
293 if ( !loc )
294 return QgsPointLocator::Match();
295
296 QgsPointLocator::Match bestMatch;
297 QgsPointLocator::MatchList edges; // for snap on intersection
298 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
299 if ( mSnappingConfig.intersectionSnapping() )
300 edges = loc->edgesInRect( pointMap, tolerance, filter, relaxed );
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, filter, false );
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() == Qgis::SnappingMode::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 if ( mSnappingConfig.intersectionSnapping() )
360 edges << loc->edgesInRect( pointMap, tolerance, filter, relaxed );
361
362 // We keep the maximum tolerance for intersection snapping and extra snapping
363 maxTolerance = std::max( maxTolerance, tolerance );
364 // To avoid yet an additional setting, on extra snappings, we use the combination of all enabled snap types
365 maxTypes = static_cast<QgsPointLocator::Type>( maxTypes | layerConfig.type );
366 }
367 }
368
369 for ( QgsVectorLayer *vl : mExtraSnapLayers )
370 {
371 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, maxTolerance );
372 _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
373 if ( mSnappingConfig.intersectionSnapping() )
374 edges << loc->edgesInRect( pointMap, maxTolerance, filter, false );
375 }
376
377 if ( mSnappingConfig.intersectionSnapping() )
378 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
379
380 return bestMatch;
381 }
382 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
383 {
384 // data from project
385 double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() );
386 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
387 QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
388
389 QList<LayerAndAreaOfInterest> layers;
390 const auto constLayers = mMapSettings.layers( true );
391 for ( QgsMapLayer *layer : constLayers )
392 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
393 layers << qMakePair( vl, aoi );
394 prepareIndex( layers, relaxed );
395
396 QgsPointLocator::MatchList edges; // for snap on intersection
397 QgsPointLocator::Match bestMatch;
398
399 for ( const LayerAndAreaOfInterest &entry : std::as_const( layers ) )
400 {
401 QgsVectorLayer *vl = entry.first;
402 if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
403 {
404 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
405 if ( mSnappingConfig.intersectionSnapping() )
406 edges << loc->edgesInRect( pointMap, tolerance, filter, relaxed );
407 }
408 }
409
410 for ( QgsVectorLayer *vl : mExtraSnapLayers )
411 {
412 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
413 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
414 if ( mSnappingConfig.intersectionSnapping() )
415 edges << loc->edgesInRect( pointMap, tolerance, filter, false );
416 }
417
418 if ( mSnappingConfig.intersectionSnapping() )
419 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
420
421 return bestMatch;
422 }
423
424 return QgsPointLocator::Match();
425}
426
427void QgsSnappingUtils::onInitFinished( bool ok )
428{
429 QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
430
431 // point locator init didn't work out - too many features!
432 // let's make the allowed area smaller for the next time
433 if ( !ok )
434 {
435 mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
436 }
437}
438
439void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed )
440{
441 // check if we need to build any index
442 QList<LayerAndAreaOfInterest> layersToIndex;
443 const auto constLayers = layers;
444 for ( const LayerAndAreaOfInterest &entry : constLayers )
445 {
446 QgsVectorLayer *vl = entry.first;
447
448 if ( vl->geometryType() == Qgis::GeometryType::Null || mStrategy == IndexNeverFull )
449 continue;
450
451 QgsPointLocator *loc = locatorForLayer( vl );
452
453 if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
454 layersToIndex << entry;
455 }
456 if ( !layersToIndex.isEmpty() )
457 {
458 // build indexes
459 QElapsedTimer t;
460 int i = 0;
461
462 if ( !relaxed )
463 {
464 t.start();
465 prepareIndexStarting( layersToIndex.count() );
466 }
467
468 for ( const LayerAndAreaOfInterest &entry : layersToIndex )
469 {
470 QgsVectorLayer *vl = entry.first;
471 QgsPointLocator *loc = locatorForLayer( vl );
472
473 if ( loc->isIndexing() && !relaxed )
474 {
476 }
477
478
479 if ( !mEnableSnappingForInvisibleFeature )
480 {
482 loc->setRenderContext( &ctx );
483 }
484
485 if ( mStrategy == IndexExtent )
486 {
487 QgsRectangle rect( mMapSettings.visibleExtent() );
488 loc->setExtent( &rect );
489 loc->init( -1, relaxed );
490 }
491 else if ( mStrategy == IndexHybrid )
492 {
493 // first time the layer is used? - let's set an initial guess about indexing
494 if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
495 {
496 long long totalFeatureCount = vl->featureCount();
497 if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
498 {
499 // index the whole layer
500 mHybridMaxAreaPerLayer[vl->id()] = -1;
501 }
502 else
503 {
504 // estimate for how big area it probably makes sense to build partial index to not exceed the limit
505 // (we may change the limit later)
506 QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
507 double totalArea = layerExtent.width() * layerExtent.height();
508 mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
509 }
510 }
511
512 double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
513 if ( indexReasonableArea == -1 )
514 {
515 // we can safely index the whole layer
516 loc->init( -1, relaxed );
517 }
518 else
519 {
520 // use area as big as we think may fit into our limit
521 QgsPointXY c = entry.second.center();
522 double halfSide = std::sqrt( indexReasonableArea ) / 2;
523 QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
524 c.x() + halfSide, c.y() + halfSide );
525 loc->setExtent( &rect );
526
527 // see if it's possible build index for this area
528 loc->init( mHybridPerLayerFeatureLimit, relaxed );
529 }
530
531 }
532 else // full index strategy
533 loc->init( relaxed );
534
535 if ( !relaxed )
537 }
538
539 if ( !relaxed )
540 {
541 QgsDebugMsgLevel( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ), 2 );
542 }
543 }
544}
545
547{
548 return mSnappingConfig;
549}
550
552{
553 mEnableSnappingForInvisibleFeature = enable;
554}
555
557{
558 if ( mSnappingConfig == config )
559 return;
560
561 if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() )
562 onIndividualLayerSettingsChanged( config.individualLayerSettings() );
563
564 mSnappingConfig = config;
565
566 emit configChanged( mSnappingConfig );
567}
568
570{
571 mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
572 emit configChanged( mSnappingConfig );
573}
574
576{
577 if ( !mCurrentLayer )
578 return QgsPointLocator::Match();
579
580 QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
581 double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
582
583 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
584 if ( !loc )
585 return QgsPointLocator::Match();
586
587 QgsPointLocator::Match bestMatch;
588 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
589 return bestMatch;
590}
591
593{
594 QString oldDestCRS = mMapSettings.destinationCrs().authid();
595 QString newDestCRS = settings.destinationCrs().authid();
596 mMapSettings = settings;
597
598 if ( newDestCRS != oldDestCRS )
600}
601
603{
604 mCurrentLayer = layer;
605}
606
608{
609 QString msg = QStringLiteral( "--- SNAPPING UTILS DUMP ---\n" );
610
611 if ( !mMapSettings.hasValidSettings() )
612 {
613 msg += QLatin1String( "invalid map settings!" );
614 return msg;
615 }
616
617 QList<LayerConfig> layers;
618
619 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
620 {
621 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer && !mCurrentLayer )
622 {
623 msg += QLatin1String( "no current layer!" );
624 return msg;
625 }
626
627 layers << LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
628 }
629 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
630 {
631 const auto constLayers = mMapSettings.layers( true );
632 for ( QgsMapLayer *layer : constLayers )
633 {
634 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
635 layers << LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
636 }
637 }
638 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
639 {
640 layers = mLayers;
641 }
642
643 const auto constLayers = layers;
644 for ( const LayerConfig &layer : constLayers )
645 {
646 msg += QString( "layer : %1\n"
647 "config: %2 tolerance %3 %4\n" )
648 .arg( layer.layer->name() )
649 .arg( layer.type ).arg( layer.tolerance ).arg( static_cast<int>( layer.unit ) );
650
651 if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid || mStrategy == IndexExtent )
652 {
653 if ( QgsPointLocator *loc = locatorForLayer( layer.layer ) )
654 {
655 QString extentStr, cachedGeoms, limit( QStringLiteral( "no max area" ) );
656 if ( const QgsRectangle *r = loc->extent() )
657 {
658 extentStr = QStringLiteral( " extent %1" ).arg( r->toString() );
659 }
660 else
661 extentStr = QStringLiteral( "full extent" );
662 if ( loc->hasIndex() )
663 cachedGeoms = QStringLiteral( "%1 feats" ).arg( loc->cachedGeometryCount() );
664 else
665 cachedGeoms = QStringLiteral( "not initialized" );
666 if ( mStrategy == IndexHybrid )
667 {
668 if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
669 {
670 double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
671 if ( maxArea != -1 )
672 limit = QStringLiteral( "max area %1" ).arg( maxArea );
673 }
674 else
675 limit = QStringLiteral( "not evaluated" );
676 }
677 msg += QStringLiteral( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
678 }
679 else
680 msg += QLatin1String( "index : ???\n" ); // should not happen
681 }
682 else
683 msg += QLatin1String( "index : NO\n" );
684 msg += QLatin1String( "-\n" );
685 }
686
687 return msg;
688}
689
690QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const
691{
692 return mMapSettings.destinationCrs();
693}
694
695void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
696{
697 mLayers.clear();
698
699 QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
700
701 for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
702 {
703 if ( i->enabled() )
704 {
705 mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType( static_cast<Qgis::SnappingTypes>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
706 }
707 }
708}
@ NoSnap
No snapping.
QFlags< SnappingType > SnappingTypes
Snapping types.
Definition qgis.h:724
@ Null
No geometry.
@ ActiveLayer
On the active layer.
@ AdvancedConfiguration
On a per layer configuration basis.
@ AllLayers
On all vector layers.
@ LineString
LineString.
@ MultiLineString
MultiLineString.
This class represents a coordinate reference system (CRS).
A geometry is the spatial representation of a feature.
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.
static QgsGeometry unaryUnion(const QVector< QgsGeometry > &geometries, const QgsGeometryParameters &parameters=QgsGeometryParameters())
Compute the unary union on a list of geometries.
Qgis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
Base class for all map layer types.
Definition qgsmaplayer.h:76
QString id
Definition qgsmaplayer.h:79
The QgsMapSettings class contains configuration for rendering of the map.
QList< QgsMapLayer * > layers(bool expandGroupLayers=false) const
Returns the list of layers which will be rendered in 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 output image size into account.
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
Transforms device coordinates to map (world) coordinates.
The class defines interface for querying point location:
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.
QFlags< Type > Types
QgsVectorLayer * layer() const
Gets associated layer.
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...
bool isIndexing() const
Returns true if the point locator is currently indexing the data.
const QgsRectangle * extent() const
Gets extent of the area point locator covers - if nullptr then it caches the whole layer.
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.
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:60
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:186
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
A rectangle specified with double values.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
bool intersects(const QgsRectangle &rect) const
Returns true when rectangle intersects with other rectangle.
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.
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.
Qgis::SnappingTypes typeFlag() const
Returns the flags type (vertices | segments | area | centroid | middle)
double maximumScale() const
Returns the max scale (i.e.
Qgis::MapToolUnit units() const
Returns the type of units.
Qgis::SnappingMode mode
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
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.
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.
QList< QgsSnappingUtils::LayerConfig > layers() const
Query layers used for snapping.
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, Qgis::MapToolUnit units)
Static function to translate tolerance value into map units.
Represents a vector layer which manages a vector based data sets.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
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:62
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
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.