24#include "moc_qgssnappingutils.cpp"
29 , mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
44 if ( !mLocators.contains( vl ) )
48 connect( vl, &QObject::destroyed,
this, [
this, vl]()
50 delete mLocators.take( vl );
53 mLocators.insert( vl, vlpl );
55 return mLocators.value( vl );
60 qDeleteAll( mLocators );
63 qDeleteAll( mTemporaryLocators );
64 mTemporaryLocators.clear();
73 QgsRectangle aoi( pointMap.
x() - tolerance, pointMap.
y() - tolerance,
74 pointMap.
x() + tolerance, pointMap.
y() + tolerance );
78 if ( loc->
isIndexing() || isIndexPrepared( loc, aoi ) )
81 return temporaryLocatorForLayer( vl, pointMap, tolerance );
86 if ( mTemporaryLocators.contains( vl ) )
87 delete mTemporaryLocators.take( vl );
89 QgsRectangle rect( pointMap.
x() - tolerance, pointMap.
y() - tolerance,
90 pointMap.
x() + tolerance, pointMap.
y() + tolerance );
92 QgsPointLocator *vlpl =
new QgsPointLocator( vl, destinationCrs(), mMapSettings.transformContext(), &rect );
95 mTemporaryLocators.insert( vl, vlpl );
96 return mTemporaryLocators.value( vl );
107 QgsRectangle aoi( areaOfInterest );
114 if ( segments.isEmpty() )
117 QSet<QgsPointXY> endpoints;
120 QVector<QgsGeometry> geoms;
121 const auto constSegments = segments;
127 m.edgePoints( pl[0], pl[1] );
129 endpoints << pl[0] << pl[1];
136 QList<QgsPointXY> newPoints;
142 if ( !endpoints.contains( p ) )
151 const auto constPl = pl;
154 if ( !endpoints.contains( p ) )
160 if ( newPoints.isEmpty() )
165 double minSqrDist = 1e20;
166 const auto constNewPoints = newPoints;
169 double sqrDist = pt.
sqrDist( p.x(), p.y() );
170 if ( sqrDist < minSqrDist )
172 minSqrDist = sqrDist;
183 if ( !candidateMatch.
isValid() || candidateMatch.
distance() > maxDistance )
203 bestMatch = candidateMatch;
212 bestMatch = candidateMatch;
224 bestMatch = candidateMatch;
231 _replaceIfBetter( bestMatch, loc->
nearestVertex( pointMap, tolerance, filter, relaxed ), tolerance );
235 _replaceIfBetter( bestMatch, loc->
nearestEdge( pointMap, tolerance, filter, relaxed ), tolerance );
242 _replaceIfBetter( bestMatch, loc->
nearestArea( pointMap, tolerance, filter, relaxed ), tolerance );
246 _replaceIfBetter( bestMatch, loc->
nearestCentroid( pointMap, tolerance, filter, relaxed ), tolerance );
250 _replaceIfBetter( bestMatch, loc->
nearestMiddleOfSegment( pointMap, tolerance, filter, relaxed ), tolerance );
254 _replaceIfBetter( bestMatch, loc->
nearestLineEndpoints( pointMap, tolerance, filter, relaxed ), tolerance );
266 return snapToMap( mMapSettings.mapToPixel().toMapCoordinates( point ), filter, relaxed );
271 return QgsRectangle( point.
x() - tolerance, point.
y() - tolerance,
272 point.
x() + tolerance, point.
y() + tolerance );
277 if ( !mMapSettings.hasValidSettings() || !mSnappingConfig.enabled() )
291 prepareIndex( QList<LayerAndAreaOfInterest>() << qMakePair( mCurrentLayer,
_areaOfInterest( pointMap, tolerance ) ), relaxed );
294 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
300 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
301 if ( mSnappingConfig.intersectionSnapping() )
302 edges = loc->
edgesInRect( pointMap, tolerance, filter, relaxed );
306 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
307 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
308 if ( mSnappingConfig.intersectionSnapping() )
309 edges << loc->
edgesInRect( pointMap, tolerance, filter,
false );
312 if ( mSnappingConfig.intersectionSnapping() )
314 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
321 QList<LayerAndAreaOfInterest>
layers;
322 QList<LayerConfig> filteredConfigs;
327 bool inRangeGlobal = ( mSnappingConfig.minimumScale() <= 0.0 || mMapSettings.scale() <= mSnappingConfig.minimumScale() )
328 && ( mSnappingConfig.maximumScale() <= 0.0 || mMapSettings.scale() >= mSnappingConfig.maximumScale() );
330 for (
const LayerConfig &layerConfig : std::as_const( mLayers ) )
345 filteredConfigs << layerConfig;
348 prepareIndex(
layers, relaxed );
352 double maxTolerance = 0;
355 for (
const LayerConfig &layerConfig : std::as_const( filteredConfigs ) )
358 if (
QgsPointLocator *loc = locatorForLayerUsingStrategy( layerConfig.layer, pointMap, tolerance ) )
360 _updateBestMatch( bestMatch, pointMap, loc, layerConfig.type, tolerance, filter, relaxed );
361 if ( mSnappingConfig.intersectionSnapping() )
362 edges << loc->
edgesInRect( pointMap, tolerance, filter, relaxed );
365 maxTolerance = std::max( maxTolerance, tolerance );
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, filter,
false );
379 if ( mSnappingConfig.intersectionSnapping() )
380 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), maxTolerance );
391 QList<LayerAndAreaOfInterest>
layers;
392 const auto constLayers = mMapSettings.layers(
true );
394 if (
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
395 layers << qMakePair( vl, aoi );
396 prepareIndex(
layers, relaxed );
401 for (
const LayerAndAreaOfInterest &entry : std::as_const(
layers ) )
404 if (
QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance ) )
406 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter, relaxed );
407 if ( mSnappingConfig.intersectionSnapping() )
408 edges << loc->
edgesInRect( pointMap, tolerance, filter, relaxed );
414 QgsPointLocator *loc = locatorForLayerUsingStrategy( vl, pointMap, tolerance );
415 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
416 if ( mSnappingConfig.intersectionSnapping() )
417 edges << loc->
edgesInRect( pointMap, tolerance, filter,
false );
420 if ( mSnappingConfig.intersectionSnapping() )
421 _replaceIfBetter( bestMatch, _findClosestSegmentIntersection( pointMap, edges ), tolerance );
429void QgsSnappingUtils::onInitFinished(
bool ok )
437 mHybridMaxAreaPerLayer[loc->
layer()->
id()] /= 4;
441void QgsSnappingUtils::prepareIndex(
const QList<LayerAndAreaOfInterest> &layers,
bool relaxed )
444 QList<LayerAndAreaOfInterest> layersToIndex;
445 const auto constLayers =
layers;
446 for (
const LayerAndAreaOfInterest &entry : constLayers )
448 QgsVectorLayer *vl = entry.first;
455 if ( !loc->
isIndexing() && !isIndexPrepared( loc, entry.second ) )
456 layersToIndex << entry;
458 if ( !layersToIndex.isEmpty() )
470 for (
const LayerAndAreaOfInterest &entry : layersToIndex )
472 QgsVectorLayer *vl = entry.first;
481 if ( !mEnableSnappingForInvisibleFeature )
489 QgsRectangle rect( mMapSettings.visibleExtent() );
491 loc->
init( -1, relaxed );
496 if ( !mHybridMaxAreaPerLayer.contains( vl->
id() ) )
499 if ( totalFeatureCount < mHybridPerLayerFeatureLimit )
502 mHybridMaxAreaPerLayer[vl->
id()] = -1;
508 QgsRectangle layerExtent = mMapSettings.layerExtentToOutputExtent( vl, vl->
extent() );
509 double totalArea = layerExtent.
width() * layerExtent.
height();
510 mHybridMaxAreaPerLayer[vl->
id()] = totalArea * mHybridPerLayerFeatureLimit / totalFeatureCount / 4;
514 double indexReasonableArea = mHybridMaxAreaPerLayer[vl->
id()];
515 if ( indexReasonableArea == -1 )
518 loc->
init( -1, relaxed );
523 QgsPointXY
c = entry.second.center();
524 double halfSide = std::sqrt( indexReasonableArea ) / 2;
525 QgsRectangle rect(
c.x() - halfSide,
c.y() - halfSide,
526 c.x() + halfSide,
c.y() + halfSide );
530 loc->
init( mHybridPerLayerFeatureLimit, relaxed );
535 loc->
init( relaxed );
543 QgsDebugMsgLevel( QStringLiteral(
"Prepare index total: %1 ms" ).arg( t.elapsed() ), 2 );
550 return mSnappingConfig;
555 mEnableSnappingForInvisibleFeature = enable;
560 if ( mSnappingConfig ==
config )
563 if ( mSnappingConfig.individualLayerSettings() !=
config.individualLayerSettings() )
564 onIndividualLayerSettingsChanged(
config.individualLayerSettings() );
573 mSnappingConfig.setEnabled( !mSnappingConfig.enabled() );
579 if ( !mCurrentLayer )
582 QgsPointXY pointMap = mMapSettings.mapToPixel().toMapCoordinates( point );
585 QgsPointLocator *loc = locatorForLayerUsingStrategy( mCurrentLayer, pointMap, tolerance );
590 _updateBestMatch( bestMatch, pointMap, loc, type, tolerance, filter,
false );
596 QString oldDestCRS = mMapSettings.destinationCrs().authid();
598 mMapSettings = settings;
600 if ( newDestCRS != oldDestCRS )
606 mCurrentLayer = layer;
611 QString msg = QStringLiteral(
"--- SNAPPING UTILS DUMP ---\n" );
613 if ( !mMapSettings.hasValidSettings() )
615 msg += QLatin1String(
"invalid map settings!" );
619 QList<LayerConfig>
layers;
625 msg += QLatin1String(
"no current layer!" );
629 layers <<
LayerConfig( mCurrentLayer, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
633 const auto constLayers = mMapSettings.layers(
true );
636 if (
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
637 layers <<
LayerConfig( vl, _snappingTypeToPointLocatorType( mSnappingConfig.typeFlag() ), mSnappingConfig.tolerance(), mSnappingConfig.units() );
645 const auto constLayers =
layers;
648 msg += QString(
"layer : %1\n"
649 "config: %2 tolerance %3 %4\n" )
650 .arg( layer.layer->name() )
651 .arg(
static_cast<int>( layer.type ) ).arg( layer.tolerance ).arg(
static_cast<int>( layer.unit ) );
657 QString extentStr, cachedGeoms, limit( QStringLiteral(
"no max area" ) );
660 extentStr = QStringLiteral(
" extent %1" ).arg( r->toString() );
663 extentStr = QStringLiteral(
"full extent" );
667 cachedGeoms = QStringLiteral(
"not initialized" );
670 if ( mHybridMaxAreaPerLayer.contains( layer.layer->id() ) )
672 double maxArea = mStrategy ==
IndexHybrid ? mHybridMaxAreaPerLayer[layer.layer->id()] : -1;
674 limit = QStringLiteral(
"max area %1" ).arg( maxArea );
677 limit = QStringLiteral(
"not evaluated" );
679 msg += QStringLiteral(
"index : YES | %1 | %2 | %3\n" ).arg( cachedGeoms, extentStr, limit );
682 msg += QLatin1String(
"index : ???\n" );
685 msg += QLatin1String(
"index : NO\n" );
686 msg += QLatin1String(
"-\n" );
697void QgsSnappingUtils::onIndividualLayerSettingsChanged(
const QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings> &layerSettings )
701 QHash<QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings>::const_iterator i;
703 for ( i = layerSettings.constBegin(); i != layerSettings.constEnd(); ++i )
707 mLayers.append(
LayerConfig( i.key(), _snappingTypeToPointLocatorType(
static_cast<Qgis::SnappingTypes>( i->typeFlag() ) ), i->tolerance(), i->units() ) );
QFlags< SnappingType > SnappingTypes
Snapping types.
@ ActiveLayer
On the active layer.
@ AdvancedConfiguration
On a per layer configuration basis.
@ AllLayers
On all vector layers.
@ MultiLineString
MultiLineString.
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 ¶meters=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.
QgsCoordinateTransformContext transformContext() const
Returns the layer data provider coordinate transform context or a default transform context if the la...
Contains configuration for rendering maps.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
Defines the interface for querying point locations.
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.
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...
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
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.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
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.
Stores configuration of snapping settings for the project.
@ PerLayer
Scale dependency using min max range per layer.
@ Disabled
No scale dependency.
@ Global
Scale dependency using global min max range.
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).
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 dataset.
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.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
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.
#define QgsDebugMsgLevel(str, level)
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.