27 , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
42 if ( !mLocators.contains( vl ) )
46 mLocators.insert( vl, vlpl );
48 return mLocators.value( vl );
53 qDeleteAll( mLocators );
56 qDeleteAll( mTemporaryLocators );
57 mTemporaryLocators.clear();
66 QgsRectangle aoi( pointMap.
x() - tolerance, pointMap.
y() - tolerance,
67 pointMap.
x() + tolerance, pointMap.
y() + tolerance );
71 if ( loc->
isIndexing() || isIndexPrepared( loc, aoi ) )
74 return temporaryLocatorForLayer( vl, pointMap, tolerance );
79 if ( mTemporaryLocators.contains( vl ) )
80 delete mTemporaryLocators.take( vl );
82 QgsRectangle rect( pointMap.
x() - tolerance, pointMap.
y() - tolerance,
83 pointMap.
x() + tolerance, pointMap.
y() + tolerance );
88 mTemporaryLocators.insert( vl, vlpl );
89 return mTemporaryLocators.value( vl );
107 if ( segments.isEmpty() )
110 QSet<QgsPointXY> endpoints;
113 QVector<QgsGeometry> geoms;
114 const auto constSegments = segments;
120 m.edgePoints( pl[0], pl[1] );
122 endpoints << pl[0] << pl[1];
129 QList<QgsPointXY> newPoints;
135 if ( !endpoints.contains( p ) )
144 const auto constPl = pl;
147 if ( !endpoints.contains( p ) )
153 if ( newPoints.isEmpty() )
158 double minSqrDist = 1e20;
159 const auto constNewPoints = newPoints;
162 double sqrDist = pt.
sqrDist( p.x(), p.y() );
163 if ( sqrDist < minSqrDist )
165 minSqrDist = sqrDist;
176 if ( !candidateMatch.
isValid() || candidateMatch.
distance() > maxDistance )
196 bestMatch = candidateMatch;
205 bestMatch = candidateMatch;
217 bestMatch = candidateMatch;
224 _replaceIfBetter( bestMatch, loc->
nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
228 _replaceIfBetter( bestMatch, loc->
nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
235 _replaceIfBetter( bestMatch, loc->
nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
239 _replaceIfBetter( bestMatch, loc->
nearestCentroid( pointMap, tolerance, filter ), tolerance );
247 _replaceIfBetter( bestMatch, loc->
nearestLineEndpoints( pointMap, tolerance, filter ), tolerance );
252 static QgsPointLocator::Types _snappingTypeToPointLocatorType( Qgis::SnappingTypes type )
254 return QgsPointLocator::Types(
static_cast<int>( type ) );
264 return QgsRectangle( point.
x() - tolerance, point.
y() - tolerance,
265 point.
x() + tolerance, point.
y() + tolerance );
277 if ( !mCurrentLayer || mSnappingConfig.
typeFlag().testFlag( Qgis::SnappingType::NoSnap ) )
282 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.
typeFlag() );
284 prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer,
_areaOfInterest( pointMap, tolerance ) ), relaxed );
287 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
293 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
297 QgsPointLocator *locEdges = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
300 edges = locEdges->
edgesInRect( pointMap, tolerance );
305 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
306 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
313 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
320 QList<LayerAndAreaOfInterest>
layers;
321 QList<LayerConfig> filteredConfigs;
329 for (
const LayerConfig &layerConfig : std::as_const( mLayers ) )
344 filteredConfigs << layerConfig;
347 prepareIndex(
layers, relaxed );
351 double maxTolerance = 0;
354 for (
const LayerConfig &layerConfig : std::as_const( filteredConfigs ) )
357 if (
QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
359 _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
366 maxTolerance = std::max( maxTolerance, tolerance );
374 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, maxTolerance );
375 _updateBestMatch( bestMatch, pointMap, loc, maxTypes, maxTolerance, filter,
false );
377 edges << loc->
edgesInRect( pointMap, maxTolerance );
381 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
389 QgsPointLocator::Types type = _snappingTypeToPointLocatorType( mSnappingConfig.
typeFlag() );
392 QList<LayerAndAreaOfInterest>
layers;
393 const auto constLayers = mMapSettings.
layers(
true );
395 if (
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
396 layers << qMakePair( vl, aoi );
397 prepareIndex(
layers, relaxed );
402 for (
const LayerAndAreaOfInterest &entry : std::as_const(
layers ) )
405 if (
QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
407 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
416 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
417 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
423 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
431 void QgsSnappingUtils::onInitFinished(
bool ok )
439 mHybridMaxAreaPerLayer[loc->
layer()->
id()] /= 4;
443 void QgsSnappingUtils::prepareIndex(
const QList<LayerAndAreaOfInterest> &layers,
bool relaxed )
446 QList<LayerAndAreaOfInterest> layersToIndex;
447 const auto constLayers =
layers;
448 for (
const LayerAndAreaOfInterest &entry : constLayers )
457 if ( !loc->
isIndexing() && !isIndexPrepared( loc, entry.second ) )
458 layersToIndex << entry;
460 if ( !layersToIndex.isEmpty() )
472 for (
const LayerAndAreaOfInterest &entry : layersToIndex )
483 if ( !mEnableSnappingForInvisibleFeature )
493 loc->
init( -1, relaxed );
498 if ( !mHybridMaxAreaPerLayer.contains( vl->
id() ) )
501 if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
504 mHybridMaxAreaPerLayer[vl->
id()] = -1;
511 double totalArea = layerExtent.
width() * layerExtent.
height();
512 mHybridMaxAreaPerLayer[vl->
id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
516 double indexReasonableArea = mHybridMaxAreaPerLayer[vl->
id()];
517 if ( indexReasonableArea == -1 )
520 loc->
init( -1, relaxed );
526 double halfSide = std::sqrt( indexReasonableArea ) / 2;
528 c.x() + halfSide,
c.y() + halfSide );
532 loc->
init( mHybridPerLayerFeatureLimit, relaxed );
537 loc->
init( relaxed );
545 QgsDebugMsg( QStringLiteral(
"Prepare index total: %1 ms" ).arg( t.elapsed() ) );
552 return mSnappingConfig;
557 mEnableSnappingForInvisibleFeature = enable;
562 if ( mSnappingConfig ==
config )
581 if ( !mCurrentLayer )
587 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
592 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
600 mMapSettings = settings;
602 if ( newDestCRS != oldDestCRS )
608 mCurrentLayer = layer;
613 QString msg = QStringLiteral(
"--- SNAPPING UTILS DUMP ---\n" );
617 msg += QLatin1String(
"invalid map settings!" );
621 QList<LayerConfig>
layers;
627 msg += QLatin1String(
"no current layer!" );
635 const auto constLayers = mMapSettings.
layers(
true );
638 if (
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
647 const auto constLayers =
layers;
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( layer.unit );
659 QString extentStr, cachedGeoms, limit( QStringLiteral(
"no max area" ) );
662 extentStr = QStringLiteral(
" extent %1" ).arg( r->toString() );
665 extentStr = QStringLiteral(
"full extent" );
669 cachedGeoms = QStringLiteral(
"not initialized" );
672 if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
674 double maxArea = mStrategy ==
IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
676 limit = QStringLiteral(
"max area %1" ).arg( maxArea );
679 limit = QStringLiteral(
"not evaluated" );
681 msg += QStringLiteral(
"index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
684 msg += QLatin1String(
"index : ???\n" );
687 msg += QLatin1String(
"index : NO\n" );
688 msg += QLatin1String(
"-\n" );
699 void QgsSnappingUtils::onIndividualLayerSettingsChanged(
const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
703 QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
705 for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
709 mLayers.append( LayerConfig( i.key(), _snappingTypeToPointLocatorType(
static_cast<Qgis::SnappingTypes
>( i->typeFlag() ) ), i->tolerance(), i->units() ) );