QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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 mLocators.insert( vl, vlpl );
47 }
48 return mLocators.value( vl );
49}
50
52{
53 qDeleteAll( mLocators );
54 mLocators.clear();
55
56 qDeleteAll( mTemporaryLocators );
57 mTemporaryLocators.clear();
58}
59
60
61QgsPointLocator *QgsSnappingUtils::locatorForLayerUsingStrategy( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
62{
63 if ( vl->geometryType() == Qgis::GeometryType::Null || mStrategy == IndexNeverFull )
64 return nullptr;
65
66 QgsRectangle aoi( pointMap.x() - tolerance, pointMap.y() - tolerance,
67 pointMap.x() + tolerance, pointMap.y() + tolerance );
68
70
71 if ( loc->isIndexing() || isIndexPrepared( loc, aoi ) )
72 return loc;
73 else
74 return temporaryLocatorForLayer( vl, pointMap, tolerance );
75}
76
77QgsPointLocator *QgsSnappingUtils::temporaryLocatorForLayer( QgsVectorLayer *vl, const QgsPointXY &pointMap, double tolerance )
78{
79 if ( mTemporaryLocators.contains( vl ) )
80 delete mTemporaryLocators.take( vl );
81
82 QgsRectangle rect( pointMap.x() - tolerance, pointMap.y() - tolerance,
83 pointMap.x() + tolerance, pointMap.y() + tolerance );
84
85 QgsPointLocator *vlpl = new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
86 connect( vlpl, &QgsPointLocator::initFinished, this, &QgsSnappingUtils::onInitFinished );
87
88 mTemporaryLocators.insert( vl, vlpl );
89 return mTemporaryLocators.value( vl );
90}
91
92bool QgsSnappingUtils::isIndexPrepared( QgsPointLocator *loc, const QgsRectangle &areaOfInterest )
93{
94 if ( mStrategy == IndexAlwaysFull && loc->hasIndex() )
95 return true;
96
97 if ( mStrategy == IndexExtent && loc->hasIndex() && ( !loc->extent() || loc->extent()->intersects( areaOfInterest ) ) )
98 return true;
99
100 QgsRectangle aoi( areaOfInterest );
101 aoi.scale( 0.999 );
102 return mStrategy == IndexHybrid && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
103}
104
105static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
106{
107 if ( segments.isEmpty() )
108 return QgsPointLocator::Match();
109
110 QSet<QgsPointXY> endpoints;
111
112 // make a geometry
113 QVector<QgsGeometry> geoms;
114 const auto constSegments = segments;
115 for ( const QgsPointLocator::Match &m : constSegments )
116 {
117 if ( m.hasEdge() )
118 {
119 QgsPolylineXY pl( 2 );
120 m.edgePoints( pl[0], pl[1] );
121 geoms << QgsGeometry::fromPolylineXY( pl );
122 endpoints << pl[0] << pl[1];
123 }
124 }
125
127
128 // get intersection points
129 QList<QgsPointXY> newPoints;
131 {
132 const auto constAsPolyline = g.asPolyline();
133 for ( const QgsPointXY &p : constAsPolyline )
134 {
135 if ( !endpoints.contains( p ) )
136 newPoints << p;
137 }
138 }
140 {
141 const auto constAsMultiPolyline = g.asMultiPolyline();
142 for ( const QgsPolylineXY &pl : constAsMultiPolyline )
143 {
144 const auto constPl = pl;
145 for ( const QgsPointXY &p : constPl )
146 {
147 if ( !endpoints.contains( p ) )
148 newPoints << p;
149 }
150 }
151 }
152
153 if ( newPoints.isEmpty() )
154 return QgsPointLocator::Match();
155
156 // find the closest points
157 QgsPointXY minP;
158 double minSqrDist = 1e20; // "infinity"
159 const auto constNewPoints = newPoints;
160 for ( const QgsPointXY &p : constNewPoints )
161 {
162 double sqrDist = pt.sqrDist( p.x(), p.y() );
163 if ( sqrDist < minSqrDist )
164 {
165 minSqrDist = sqrDist;
166 minP = p;
167 }
168 }
169
170 return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
171}
172
173static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
174{
175 // is candidate match relevant?
176 if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
177 return;
178
179 // is candidate match actually better?
180 if ( bestMatch.isValid() && bestMatch.type() == candidateMatch.type() && bestMatch.distance() - 10e-6 < candidateMatch.distance() )
181 return;
182
183 // ORDER
184 // LineEndpoint
185 // Vertex, Intersection
186 // Middle
187 // Centroid
188 // Edge
189 // Area
190
191 // first line endpoint -- these are like vertex matches, but even more strict
192 if ( ( bestMatch.type() & QgsPointLocator::LineEndpoint ) && !( candidateMatch.type() & QgsPointLocator::LineEndpoint ) )
193 return;
194 if ( candidateMatch.type() & QgsPointLocator::LineEndpoint )
195 {
196 bestMatch = candidateMatch;
197 return;
198 }
199
200 // Second Vertex, or intersection
201 if ( ( bestMatch.type() & QgsPointLocator::Vertex ) && !( candidateMatch.type() & QgsPointLocator::Vertex ) )
202 return;
203 if ( candidateMatch.type() & QgsPointLocator::Vertex )
204 {
205 bestMatch = candidateMatch;
206 return;
207 }
208
209 // prefer vertex, centroid, middle matches over edge matches (even if they are closer)
210 if ( ( bestMatch.type() & QgsPointLocator::Centroid || bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Edge || candidateMatch.type() & QgsPointLocator::Area ) )
211 return;
212
213 // prefer middle matches over centroid matches (even if they are closer)
214 if ( ( bestMatch.type() & QgsPointLocator::MiddleOfSegment ) && ( candidateMatch.type() & QgsPointLocator::Centroid ) )
215 return;
216
217 bestMatch = candidateMatch; // the other match is better!
218}
219
220static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter, bool relaxed )
221{
222 if ( type & QgsPointLocator::Vertex )
223 {
224 _replaceIfBetter( bestMatch, loc->nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
225 }
226 if ( bestMatch.type() != QgsPointLocator::Vertex && ( type & QgsPointLocator::Edge ) )
227 {
228 _replaceIfBetter( bestMatch, loc->nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
229 }
230 if ( bestMatch.type() != QgsPointLocator::Vertex && bestMatch.type() != QgsPointLocator::Edge && ( type & QgsPointLocator::Area ) )
231 {
232 // if edges were detected, set tolerance to 0 to only do pointInPolygon (and avoid redo nearestEdge)
233 if ( type & QgsPointLocator::Edge )
234 tolerance = 0;
235 _replaceIfBetter( bestMatch, loc->nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
236 }
237 if ( type & QgsPointLocator::Centroid )
238 {
239 _replaceIfBetter( bestMatch, loc->nearestCentroid( pointMap, tolerance, filter, relaxed ), tolerance );
240 }
242 {
243 _replaceIfBetter( bestMatch, loc->nearestMiddleOfSegment( pointMap, tolerance, filter, relaxed ), tolerance );
244 }
246 {
247 _replaceIfBetter( bestMatch, loc->nearestLineEndpoints( pointMap, tolerance, filter, relaxed ), tolerance );
248 }
249}
250
251
252static QgsPointLocator::Types _snappingTypeToPointLocatorType( Qgis::SnappingTypes type )
253{
254 return QgsPointLocator::Types( static_cast<int>( type ) );
255}
256
258{
259 return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
260}
261
262inline QgsRectangle _areaOfInterest( const QgsPointXY &point, double tolerance )
263{
264 return QgsRectangle( point.x() - tolerance, point.y() - tolerance,
265 point.x() + tolerance, point.y() + tolerance );
266}
267
269{
270 if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
271 {
272 return QgsPointLocator::Match();
273 }
274
275 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
276 {
277 if ( !mCurrentLayer || mSnappingConfig.typeFlag().testFlag( Qgis::SnappingType::NoSnap ) )
278 return QgsPointLocator::Match();
279
280 // data from project
281 double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), mCurrentLayer, mMapSettings, mSnappingConfig.units() );
282 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
283
284 prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer, _areaOfInterest( pointMap, tolerance ) ), relaxed );
285
286 // use ad-hoc locator
287 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
288 if ( !loc )
289 return QgsPointLocator::Match();
290
291 QgsPointLocator::Match bestMatch;
292 QgsPointLocator::MatchList edges; // for snap on intersection
293 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
294
295 if ( mSnappingConfig.intersectionSnapping() )
296 {
297 QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
298 if ( !locEdges )
299 return QgsPointLocator::Match();
300 edges = locEdges->edgesInRect( pointMap, tolerance );
301 }
302
303 for ( QgsVectorLayer *vl : mExtraSnapLayers )
304 {
305 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
306 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
307 if ( mSnappingConfig.intersectionSnapping() )
308 edges << loc->edgesInRect( pointMap, tolerance );
309 }
310
311 if ( mSnappingConfig.intersectionSnapping() )
312 {
313 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
314 }
315
316 return bestMatch;
317 }
318 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
319 {
320 QList<LayerAndAreaOfInterest> layers;
321 QList<LayerConfig> filteredConfigs;
322
323 //maximum scale is the one with smallest denominator
324 //minimum scale is the one with highest denominator
325 //So : maxscale < range on which snapping is enabled < minscale
326 bool inRangeGlobal = ( mSnappingConfig.minimumScale() <= 0.0 || mMapSettings.scale() <= mSnappingConfig.minimumScale() )
327 && ( mSnappingConfig.maximumScale() <= 0.0 || mMapSettings.scale() >= mSnappingConfig.maximumScale() );
328
329 for ( const LayerConfig &layerConfig : std::as_const( mLayers ) )
330 {
331 QgsSnappingConfig::IndividualLayerSettings layerSettings = mSnappingConfig.individualLayerSettings( layerConfig.layer );
332
333 bool inRangeLayer = ( layerSettings.minimumScale() <= 0.0 || mMapSettings.scale() <= layerSettings.minimumScale() )
334 && ( layerSettings.maximumScale() <= 0.0 || mMapSettings.scale() >= layerSettings.maximumScale() );
335
336 //If limit to scale is disabled, snapping activated on all layer
337 //If no per layer config is set use the global one, otherwise use the layer config
338 if ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Disabled
339 || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::Global && inRangeGlobal )
340 || ( mSnappingConfig.scaleDependencyMode() == QgsSnappingConfig::PerLayer && inRangeLayer ) )
341 {
342 double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
343 layers << qMakePair( layerConfig.layer, _areaOfInterest( pointMap, tolerance ) );
344 filteredConfigs << layerConfig;
345 }
346 }
347 prepareIndex( layers, relaxed );
348
349 QgsPointLocator::Match bestMatch;
350 QgsPointLocator::MatchList edges; // for snap on intersection
351 double maxTolerance = 0;
353
354 for ( const LayerConfig &layerConfig : std::as_const( filteredConfigs ) )
355 {
356 double tolerance = QgsTolerance::toleranceInProjectUnits( layerConfig.tolerance, layerConfig.layer, mMapSettings, layerConfig.unit );
357 if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
358 {
359 _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
360
361 if ( mSnappingConfig.intersectionSnapping() )
362 {
363 edges << loc->edgesInRect( pointMap, tolerance );
364 }
365 // We keep the maximum tolerance for intersection snapping and extra snapping
366 maxTolerance = std::max( maxTolerance, tolerance );
367 // To avoid yet an additional setting, on extra snappings, we use the combination of all enabled snap types
368 maxTypes = static_cast<QgsPointLocator::Type>( maxTypes | layerConfig.type );
369 }
370 }
371
372 for ( QgsVectorLayer *vl : mExtraSnapLayers )
373 {
374 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, maxTolerance );
375 _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter, false );
376 if ( mSnappingConfig.intersectionSnapping() )
377 edges << loc->edgesInRect( pointMap, maxTolerance );
378 }
379
380 if ( mSnappingConfig.intersectionSnapping() )
381 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
382
383 return bestMatch;
384 }
385 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
386 {
387 // data from project
388 double tolerance = QgsTolerance::toleranceInProjectUnits( mSnappingConfig.tolerance(), nullptr, mMapSettings, mSnappingConfig.units() );
389 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() );
390 QgsRectangle aoi = _areaOfInterest( pointMap, tolerance );
391
392 QList<LayerAndAreaOfInterest> layers;
393 const auto constLayers = mMapSettings.layers( true );
394 for ( QgsMapLayer *layer : constLayers )
395 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
396 layers << qMakePair( vl, aoi );
397 prepareIndex( layers, relaxed );
398
399 QgsPointLocator::MatchList edges; // for snap on intersection
400 QgsPointLocator::Match bestMatch;
401
402 for ( const LayerAndAreaOfInterest &entry : std::as_const( layers ) )
403 {
404 QgsVectorLayer *vl = entry.first;
405 if ( QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
406 {
407 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
408
409 if ( mSnappingConfig.intersectionSnapping() )
410 edges << loc->edgesInRect( pointMap, tolerance );
411 }
412 }
413
414 for ( QgsVectorLayer *vl : mExtraSnapLayers )
415 {
416 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
417 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
418 if ( mSnappingConfig.intersectionSnapping() )
419 edges << loc->edgesInRect( pointMap, tolerance );
420 }
421
422 if ( mSnappingConfig.intersectionSnapping() )
423 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
424
425 return bestMatch;
426 }
427
428 return QgsPointLocator::Match();
429}
430
431void QgsSnappingUtils::onInitFinished( bool ok )
432{
433 QgsPointLocator *loc = static_cast<QgsPointLocator *>( sender() );
434
435 // point locator init didn't work out - too many features!
436 // let's make the allowed area smaller for the next time
437 if ( !ok )
438 {
439 mHybridMaxAreaPerLayer[loc->layer()->id()] /= 4;
440 }
441}
442
443void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers, bool relaxed )
444{
445 // check if we need to build any index
446 QList<LayerAndAreaOfInterest> layersToIndex;
447 const auto constLayers = layers;
448 for ( const LayerAndAreaOfInterest &entry : constLayers )
449 {
450 QgsVectorLayer *vl = entry.first;
451
452 if ( vl->geometryType() == Qgis::GeometryType::Null || mStrategy == IndexNeverFull )
453 continue;
454
455 QgsPointLocator *loc = locatorForLayer( vl );
456
457 if ( !loc->isIndexing() && !isIndexPrepared( loc, entry.second ) )
458 layersToIndex << entry;
459 }
460 if ( !layersToIndex.isEmpty() )
461 {
462 // build indexes
463 QElapsedTimer t;
464 int i = 0;
465
466 if ( !relaxed )
467 {
468 t.start();
469 prepareIndexStarting( layersToIndex.count() );
470 }
471
472 for ( const LayerAndAreaOfInterest &entry : layersToIndex )
473 {
474 QgsVectorLayer *vl = entry.first;
475 QgsPointLocator *loc = locatorForLayer( vl );
476
477 if ( loc->isIndexing() && !relaxed )
478 {
480 }
481
482
483 if ( !mEnableSnappingForInvisibleFeature )
484 {
486 loc->setRenderContext( &ctx );
487 }
488
489 if ( mStrategy == IndexExtent )
490 {
491 QgsRectangle rect( mMapSettings.visibleExtent() );
492 loc->setExtent( &rect );
493 loc->init( -1, relaxed );
494 }
495 else if ( mStrategy == IndexHybrid )
496 {
497 // first time the layer is used? - let's set an initial guess about indexing
498 if ( !mHybridMaxAreaPerLayer.contains( vl->id() ) )
499 {
500 long long totalFeatureCount = vl->featureCount();
501 if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
502 {
503 // index the whole layer
504 mHybridMaxAreaPerLayer[vl->id()] = -1;
505 }
506 else
507 {
508 // estimate for how big area it probably makes sense to build partial index to not exceed the limit
509 // (we may change the limit later)
510 QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->extent() );
511 double totalArea = layerExtent.width() * layerExtent.height();
512 mHybridMaxAreaPerLayer[vl->id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
513 }
514 }
515
516 double indexReasonableArea = mHybridMaxAreaPerLayer[vl->id()];
517 if ( indexReasonableArea == -1 )
518 {
519 // we can safely index the whole layer
520 loc->init( -1, relaxed );
521 }
522 else
523 {
524 // use area as big as we think may fit into our limit
525 QgsPointXY c = entry.second.center();
526 double halfSide = std::sqrt( indexReasonableArea ) / 2;
527 QgsRectangle rect( c.x() - halfSide, c.y() - halfSide,
528 c.x() + halfSide, c.y() + halfSide );
529 loc->setExtent( &rect );
530
531 // see if it's possible build index for this area
532 loc->init( mHybridPerLayerFeatureLimit, relaxed );
533 }
534
535 }
536 else // full index strategy
537 loc->init( relaxed );
538
539 if ( !relaxed )
541 }
542
543 if ( !relaxed )
544 {
545 QgsDebugMsgLevel( QStringLiteral( "Prepare index total: %1 ms" ).arg( t.elapsed() ), 2 );
546 }
547 }
548}
549
551{
552 return mSnappingConfig;
553}
554
556{
557 mEnableSnappingForInvisibleFeature = enable;
558}
559
561{
562 if ( mSnappingConfig == config )
563 return;
564
565 if ( mSnappingConfig.individualLayerSettings() != config.individualLayerSettings() )
566 onIndividualLayerSettingsChanged( config.individualLayerSettings() );
567
568 mSnappingConfig = config;
569
570 emit configChanged( mSnappingConfig );
571}
572
574{
575 mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
576 emit configChanged( mSnappingConfig );
577}
578
580{
581 if ( !mCurrentLayer )
582 return QgsPointLocator::Match();
583
584 QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
585 double tolerance = QgsTolerance::vertexSearchRadius( mMapSettings );
586
587 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
588 if ( !loc )
589 return QgsPointLocator::Match();
590
591 QgsPointLocator::Match bestMatch;
592 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, false );
593 return bestMatch;
594}
595
597{
598 QString oldDestCRS = mMapSettings.destinationCrs().authid();
599 QString newDestCRS = settings.destinationCrs().authid();
600 mMapSettings = settings;
601
602 if ( newDestCRS != oldDestCRS )
604}
605
607{
608 mCurrentLayer = layer;
609}
610
612{
613 QString msg = QStringLiteral( "--- SNAPPING UTILS DUMP ---\n" );
614
615 if ( !mMapSettings.hasValidSettings() )
616 {
617 msg += QLatin1String( "invalid map settings!" );
618 return msg;
619 }
620
621 QList<LayerConfig> layers;
622
623 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer )
624 {
625 if ( mSnappingConfig.mode() == Qgis::SnappingMode::ActiveLayer && !mCurrentLayer )
626 {
627 msg += QLatin1String( "no current layer!" );
628 return msg;
629 }
630
631 layers << LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
632 }
633 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AllLayers )
634 {
635 const auto constLayers = mMapSettings.layers( true );
636 for ( QgsMapLayer *layer : constLayers )
637 {
638 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
639 layers << LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
640 }
641 }
642 else if ( mSnappingConfig.mode() == Qgis::SnappingMode::AdvancedConfiguration )
643 {
644 layers = mLayers;
645 }
646
647 const auto constLayers = layers;
648 for ( const LayerConfig &layer : constLayers )
649 {
650 msg += QString( "layer : %1\n"
651 "config: %2 tolerance %3 %4\n" )
652 .arg( layer.layer->name() )
653 .arg( layer.type ).arg( layer.tolerance ).arg( static_cast<int>( layer.unit ) );
654
655 if ( mStrategy == IndexAlwaysFull || mStrategy == IndexHybrid || mStrategy == IndexExtent )
656 {
657 if ( QgsPointLocator *loc = locatorForLayer( layer.layer ) )
658 {
659 QString extentStr, cachedGeoms, limit( QStringLiteral( "no max area" ) );
660 if ( const QgsRectangle *r = loc->extent() )
661 {
662 extentStr = QStringLiteral( " extent %1" ).arg( r->toString() );
663 }
664 else
665 extentStr = QStringLiteral( "full extent" );
666 if ( loc->hasIndex() )
667 cachedGeoms = QStringLiteral( "%1 feats" ).arg( loc->cachedGeometryCount() );
668 else
669 cachedGeoms = QStringLiteral( "not initialized" );
670 if ( mStrategy == IndexHybrid )
671 {
672 if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
673 {
674 double maxArea = mStrategy == IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
675 if ( maxArea != -1 )
676 limit = QStringLiteral( "max area %1" ).arg( maxArea );
677 }
678 else
679 limit = QStringLiteral( "not evaluated" );
680 }
681 msg += QStringLiteral( "index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
682 }
683 else
684 msg += QLatin1String( "index : ???\n" ); // should not happen
685 }
686 else
687 msg += QLatin1String( "index : NO\n" );
688 msg += QLatin1String( "-\n" );
689 }
690
691 return msg;
692}
693
694QgsCoordinateReferenceSystem QgsSnappingUtils::destinationCrs() const
695{
696 return mMapSettings.destinationCrs();
697}
698
699void QgsSnappingUtils::onIndividualLayerSettingsChanged( const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
700{
701 mLayers.clear();
702
703 QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
704
705 for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
706 {
707 if ( i->enabled() )
708 {
709 mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType( static_cast<Qgis::SnappingTypes>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
710 }
711 }
712}
@ 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.
double width() const
Returns the width of the rectangle.
double height() const
Returns the height of the 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:39
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.