Skip to content

Commit 66b0e59

Browse files
committed
Fix snapping on invisible geometry
1 parent 5e33d7d commit 66b0e59

14 files changed

+273
-125
lines changed

python/core/qgspointlocator.sip.in

+7-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ Get extent of the area point locator covers - if null then it caches the whole l
7171
Configure extent - if not null, it will index only that area
7272

7373
.. versionadded:: 2.14
74+
%End
75+
76+
void setRenderContext( const QgsRenderContext &context );
77+
%Docstring
78+
Configure render context - if not null, it will use to index only visible feature
79+
80+
.. versionadded:: 3.2
7481
%End
7582

7683
enum Type
@@ -199,7 +206,6 @@ Override of edgesInRect that construct rectangle from a center point and toleran
199206
find out if the point is in any polygons
200207
%End
201208

202-
203209
int cachedGeometryCount() const;
204210
%Docstring
205211
Return how many geometries are cached in the index

python/core/qgssnappingutils.sip.in

+10-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ which keeps the configuration in sync with map canvas (e.g. current view, active
3535
%End
3636
public:
3737

38-
QgsSnappingUtils( QObject *parent /TransferThis/ = 0 );
38+
QgsSnappingUtils( QObject *parent /TransferThis/ = 0, bool enableSnappingForInvisibleFeature = true );
3939
%Docstring
4040
Constructor for QgsSnappingUtils
4141
%End
@@ -139,6 +139,15 @@ Get extra information about the instance
139139
QgsSnappingConfig config() const;
140140
%Docstring
141141
The snapping configuration controls the behavior of this object
142+
%End
143+
144+
void setEnableSnappingForInvisibleFeature( bool enable );
145+
%Docstring
146+
Set if invisible features must be snapped or not.
147+
148+
:param enable: Enable or not this feature
149+
150+
.. versionadded:: 3.2
142151
%End
143152

144153
public slots:

python/gui/qgsmapcanvassnappingutils.sip.in

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010

1111

12+
1213
class QgsMapCanvasSnappingUtils : QgsSnappingUtils
1314
{
1415
%Docstring

resources/qgis_global_settings.ini

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ digitizing\default_snap_type=1
2020
# 2 = Project units
2121
digitizing\default_snapping_tolerance_unit=1
2222

23+
# Snap on invisble feature
24+
digitizing\snap_invisible_feature=false
25+
2326
# Default XYZ tile servers to include
2427
connections-xyz\OpenStreetMap\authcfg=
2528
connections-xyz\OpenStreetMap\password=

src/app/qgsoptions.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti
971971

972972
mSnappingMarkerColorButton->setColor( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_color" ), QColor( Qt::magenta ) ).value<QColor>() );
973973
mSnappingTooltipsCheckbox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_tooltip" ), false ).toBool() );
974+
mEnableSnappingOnInvisibleFeatureCheckbox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() );
974975

975976
//vertex marker
976977
mMarkersOnlyForSelectedCheckBox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/marker_only_for_selected" ), true ).toBool() );
@@ -1483,6 +1484,7 @@ void QgsOptions::saveOptions()
14831484

14841485
mSettings->setValue( QStringLiteral( "/qgis/digitizing/snap_color" ), mSnappingMarkerColorButton->color() );
14851486
mSettings->setValue( QStringLiteral( "/qgis/digitizing/snap_tooltip" ), mSnappingTooltipsCheckbox->isChecked() );
1487+
mSettings->setValue( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), mEnableSnappingOnInvisibleFeatureCheckbox->isChecked() );
14861488

14871489
mSettings->setValue( QStringLiteral( "/qgis/digitizing/marker_only_for_selected" ), mMarkersOnlyForSelectedCheckBox->isChecked() );
14881490

src/core/layertree/qgslayertreemodellegendnode.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ bool QgsSymbolLegendNode::setData( const QVariant &value, int role )
331331
vlayer->renderer()->checkLegendSymbolItem( mItem.ruleKey(), value == Qt::Checked );
332332

333333
emit dataChanged();
334+
emit vlayer->styleChanged();
334335

335336
vlayer->triggerRepaint();
336337

src/core/qgspointlocator.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "qgswkbptr.h"
2222
#include "qgis.h"
2323
#include "qgslogger.h"
24+
#include "qgsrenderer.h"
2425

2526
#include <SpatialIndex.h>
2627

@@ -636,6 +637,9 @@ QgsPointLocator::QgsPointLocator( QgsVectorLayer *layer, const QgsCoordinateRefe
636637
connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsPointLocator::onFeatureDeleted );
637638
connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsPointLocator::onGeometryChanged );
638639
connect( mLayer, &QgsVectorLayer::dataChanged, this, &QgsPointLocator::destroyIndex );
640+
connect( mLayer, &QgsVectorLayer::rendererChanged, this, &QgsPointLocator::destroyIndex );
641+
connect( mLayer, &QgsVectorLayer::styleChanged, this, &QgsPointLocator::destroyIndex );
642+
connect( mLayer, &QgsVectorLayer::layerModified, this, &QgsPointLocator::destroyIndex );
639643
}
640644

641645

@@ -661,6 +665,12 @@ void QgsPointLocator::setExtent( const QgsRectangle *extent )
661665
destroyIndex();
662666
}
663667

668+
void QgsPointLocator::setRenderContext( const QgsRenderContext &context )
669+
{
670+
mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( context ) );
671+
672+
destroyIndex();
673+
}
664674

665675
bool QgsPointLocator::init( int maxFeaturesToIndex )
666676
{
@@ -686,6 +696,7 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
686696

687697
QgsFeatureRequest request;
688698
request.setSubsetOfAttributes( QgsAttributeList() );
699+
689700
if ( mExtent )
690701
{
691702
QgsRectangle rect = *mExtent;
@@ -704,13 +715,40 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
704715
}
705716
request.setFilterRect( rect );
706717
}
718+
719+
bool filter = false;
720+
std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
721+
QgsRenderContext *ctx = nullptr;
722+
if ( mContext )
723+
{
724+
mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
725+
ctx = mContext.get();
726+
if ( renderer )
727+
{
728+
// setup scale for scale dependent visibility (rule based)
729+
renderer->startRender( *ctx, mLayer->fields() );
730+
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
731+
request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
732+
}
733+
}
734+
707735
QgsFeatureIterator fi = mLayer->getFeatures( request );
708736
int indexedCount = 0;
737+
709738
while ( fi.nextFeature( f ) )
710739
{
711740
if ( !f.hasGeometry() )
712741
continue;
713742

743+
if ( ctx && renderer )
744+
{
745+
ctx->expressionContext().setFeature( f );
746+
if ( filter && !renderer->willRenderFeature( f, *ctx ) )
747+
{
748+
continue;
749+
}
750+
}
751+
714752
if ( mTransform.isValid() )
715753
{
716754
try
@@ -761,6 +799,12 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
761799
QgsPointLocator_Stream stream( dataList );
762800
mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
763801
leafCapacity, dimension, variant, indexId );
802+
803+
if ( ctx && renderer )
804+
{
805+
renderer->stopRender( *ctx );
806+
renderer.release();
807+
}
764808
return true;
765809
}
766810

@@ -832,6 +876,7 @@ void QgsPointLocator::onFeatureDeleted( QgsFeatureId fid )
832876
mRTree->deleteData( rect2region( mGeoms[fid]->boundingBox() ), fid );
833877
delete mGeoms.take( fid );
834878
}
879+
835880
}
836881

837882
void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )

src/core/qgspointlocator.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818

1919
class QgsPointXY;
2020
class QgsVectorLayer;
21+
class QgsFeatureRenderer;
22+
class QgsRenderContext;
2123

2224
#include "qgis_core.h"
2325
#include "qgsfeature.h"
2426
#include "qgspointxy.h"
2527
#include "qgscoordinatereferencesystem.h"
2628
#include "qgscoordinatetransform.h"
29+
#include <memory>
2730

2831
class QgsPointLocator_VisitorNearestVertex;
2932
class QgsPointLocator_VisitorNearestEdge;
@@ -92,6 +95,12 @@ class CORE_EXPORT QgsPointLocator : public QObject
9295
*/
9396
void setExtent( const QgsRectangle *extent );
9497

98+
/**
99+
* Configure render context - if not null, it will use to index only visible feature
100+
* \since QGIS 3.2
101+
*/
102+
void setRenderContext( const QgsRenderContext &context );
103+
95104
/**
96105
* The type of a snap result or the filter type for a snap request.
97106
*/
@@ -251,8 +260,6 @@ class CORE_EXPORT QgsPointLocator : public QObject
251260
//! find out if the point is in any polygons
252261
MatchList pointInPolygon( const QgsPointXY &point );
253262

254-
//
255-
256263
/**
257264
* Return how many geometries are cached in the index
258265
* \since QGIS 2.14
@@ -278,11 +285,15 @@ class CORE_EXPORT QgsPointLocator : public QObject
278285
//! flag whether the layer is currently empty (i.e. mRTree is null but it is not necessary to rebuild it)
279286
bool mIsEmptyLayer;
280287

288+
QgsFeatureIds mFeatureIds;
289+
281290
//! R-tree containing spatial index
282291
QgsCoordinateTransform mTransform;
283292
QgsVectorLayer *mLayer = nullptr;
284293
QgsRectangle *mExtent = nullptr;
285294

295+
std::unique_ptr<QgsRenderContext> mContext;
296+
286297
friend class QgsPointLocator_VisitorNearestVertex;
287298
friend class QgsPointLocator_VisitorNearestEdge;
288299
friend class QgsPointLocator_VisitorArea;

src/core/qgssnappingutils.cpp

+15-5
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
#include "qgsproject.h"
2020
#include "qgsvectorlayer.h"
2121
#include "qgslogger.h"
22+
#include "qgsrenderer.h"
2223

23-
QgsSnappingUtils::QgsSnappingUtils( QObject *parent )
24+
QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
2425
: QObject( parent )
2526
, mSnappingConfig( QgsProject::instance() )
27+
, mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
2628
{
2729
}
2830

@@ -92,7 +94,6 @@ bool QgsSnappingUtils::isIndexPrepared( QgsVectorLayer *vl, const QgsRectangle &
9294
return ( mStrategy == IndexHybrid || mStrategy == IndexExtent ) && loc->hasIndex() && ( !loc->extent() || loc->extent()->contains( aoi ) ); // the index - even if it exists - is not suitable
9395
}
9496

95-
9697
static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY &pt, const QgsPointLocator::MatchList &segments )
9798
{
9899
if ( segments.isEmpty() )
@@ -156,9 +157,9 @@ static QgsPointLocator::Match _findClosestSegmentIntersection( const QgsPointXY
156157
return QgsPointLocator::Match( QgsPointLocator::Vertex, nullptr, 0, std::sqrt( minSqrDist ), minP );
157158
}
158159

159-
160160
static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
161161
{
162+
162163
// is candidate match relevant?
163164
if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
164165
return;
@@ -174,7 +175,6 @@ static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointL
174175
bestMatch = candidateMatch; // the other match is better!
175176
}
176177

177-
178178
static void _updateBestMatch( QgsPointLocator::Match &bestMatch, const QgsPointXY &pointMap, QgsPointLocator *loc, QgsPointLocator::Types type, double tolerance, QgsPointLocator::MatchFilter *filter )
179179
{
180180
if ( type & QgsPointLocator::Vertex )
@@ -329,7 +329,6 @@ QgsPointLocator::Match QgsSnappingUtils::snapToMap( const QgsPointXY &pointMap,
329329
return QgsPointLocator::Match();
330330
}
331331

332-
333332
void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers )
334333
{
335334
if ( mIsIndexing )
@@ -341,6 +340,7 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
341340
Q_FOREACH ( const LayerAndAreaOfInterest &entry, layers )
342341
{
343342
QgsVectorLayer *vl = entry.first;
343+
344344
if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
345345
continue;
346346

@@ -359,7 +359,12 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
359359
QgsVectorLayer *vl = entry.first;
360360
QTime tt;
361361
tt.start();
362+
362363
QgsPointLocator *loc = locatorForLayer( vl );
364+
365+
if ( !mEnableSnappingForInvisibleFeature )
366+
loc->setRenderContext( QgsRenderContext::fromMapSettings( mMapSettings ) );
367+
363368
if ( mStrategy == IndexExtent )
364369
{
365370
QgsRectangle rect( mMapSettings.extent() );
@@ -428,6 +433,11 @@ QgsSnappingConfig QgsSnappingUtils::config() const
428433
return mSnappingConfig;
429434
}
430435

436+
void QgsSnappingUtils::setEnableSnappingForInvisibleFeature( bool enable )
437+
{
438+
mEnableSnappingForInvisibleFeature = enable;
439+
}
440+
431441
void QgsSnappingUtils::setConfig( const QgsSnappingConfig &config )
432442
{
433443
if ( mSnappingConfig == config )

src/core/qgssnappingutils.h

+14-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
5353
public:
5454

5555
//! Constructor for QgsSnappingUtils
56-
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr );
56+
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true );
5757
~QgsSnappingUtils() override;
5858

5959
// main actions
@@ -159,6 +159,15 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
159159
*/
160160
QgsSnappingConfig config() const;
161161

162+
/**
163+
* Set if invisible features must be snapped or not.
164+
*
165+
* \param enable Enable or not this feature
166+
*
167+
* \since QGIS 3.2
168+
*/
169+
void setEnableSnappingForInvisibleFeature( bool enable );
170+
162171
public slots:
163172

164173
/**
@@ -242,6 +251,10 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
242251

243252
//! internal flag that an indexing process is going on. Prevents starting two processes in parallel.
244253
bool mIsIndexing = false;
254+
255+
//! Disable or not the snapping on all features. By default is always true except for non visible features on map canvas.
256+
bool mEnableSnappingForInvisibleFeature = true;
257+
245258
};
246259

247260

0 commit comments

Comments
 (0)