Skip to content
Permalink
Browse files

Fix snapping on invisible geometry

  • Loading branch information
lbartoletti committed Apr 23, 2018
1 parent 5e33d7d commit 66b0e59fb993050018ab60c61b816820b2c53c0c
@@ -71,6 +71,13 @@ Get extent of the area point locator covers - if null then it caches the whole l
Configure extent - if not null, it will index only that area

.. versionadded:: 2.14
%End

void setRenderContext( const QgsRenderContext &context );
%Docstring
Configure render context - if not null, it will use to index only visible feature

.. versionadded:: 3.2
%End

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


int cachedGeometryCount() const;
%Docstring
Return how many geometries are cached in the index
@@ -35,7 +35,7 @@ which keeps the configuration in sync with map canvas (e.g. current view, active
%End
public:

QgsSnappingUtils( QObject *parent /TransferThis/ = 0 );
QgsSnappingUtils( QObject *parent /TransferThis/ = 0, bool enableSnappingForInvisibleFeature = true );
%Docstring
Constructor for QgsSnappingUtils
%End
@@ -139,6 +139,15 @@ Get extra information about the instance
QgsSnappingConfig config() const;
%Docstring
The snapping configuration controls the behavior of this object
%End

void setEnableSnappingForInvisibleFeature( bool enable );
%Docstring
Set if invisible features must be snapped or not.

:param enable: Enable or not this feature

.. versionadded:: 3.2
%End

public slots:
@@ -9,6 +9,7 @@




class QgsMapCanvasSnappingUtils : QgsSnappingUtils
{
%Docstring
@@ -20,6 +20,9 @@ digitizing\default_snap_type=1
# 2 = Project units
digitizing\default_snapping_tolerance_unit=1

# Snap on invisble feature
digitizing\snap_invisible_feature=false

# Default XYZ tile servers to include
connections-xyz\OpenStreetMap\authcfg=
connections-xyz\OpenStreetMap\password=
@@ -971,6 +971,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti

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

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

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

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

@@ -331,6 +331,7 @@ bool QgsSymbolLegendNode::setData( const QVariant &value, int role )
vlayer->renderer()->checkLegendSymbolItem( mItem.ruleKey(), value == Qt::Checked );

emit dataChanged();
emit vlayer->styleChanged();

vlayer->triggerRepaint();

@@ -21,6 +21,7 @@
#include "qgswkbptr.h"
#include "qgis.h"
#include "qgslogger.h"
#include "qgsrenderer.h"

#include <SpatialIndex.h>

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


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

void QgsPointLocator::setRenderContext( const QgsRenderContext &context )
{
mContext = std::unique_ptr<QgsRenderContext>( new QgsRenderContext( context ) );

destroyIndex();
}

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

QgsFeatureRequest request;
request.setSubsetOfAttributes( QgsAttributeList() );

if ( mExtent )
{
QgsRectangle rect = *mExtent;
@@ -704,13 +715,40 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
}
request.setFilterRect( rect );
}

bool filter = false;
std::unique_ptr< QgsFeatureRenderer > renderer( mLayer->renderer() ? mLayer->renderer()->clone() : nullptr );
QgsRenderContext *ctx = nullptr;
if ( mContext )
{
mContext->expressionContext() << QgsExpressionContextUtils::layerScope( mLayer );
ctx = mContext.get();
if ( renderer )
{
// setup scale for scale dependent visibility (rule based)
renderer->startRender( *ctx, mLayer->fields() );
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
request.setSubsetOfAttributes( renderer->usedAttributes( *ctx ), mLayer->fields() );
}
}

QgsFeatureIterator fi = mLayer->getFeatures( request );
int indexedCount = 0;

while ( fi.nextFeature( f ) )
{
if ( !f.hasGeometry() )
continue;

if ( ctx && renderer )
{
ctx->expressionContext().setFeature( f );
if ( filter && !renderer->willRenderFeature( f, *ctx ) )
{
continue;
}
}

if ( mTransform.isValid() )
{
try
@@ -761,6 +799,12 @@ bool QgsPointLocator::rebuildIndex( int maxFeaturesToIndex )
QgsPointLocator_Stream stream( dataList );
mRTree = RTree::createAndBulkLoadNewRTree( RTree::BLM_STR, stream, *mStorage, fillFactor, indexCapacity,
leafCapacity, dimension, variant, indexId );

if ( ctx && renderer )
{
renderer->stopRender( *ctx );
renderer.release();
}
return true;
}

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

}

void QgsPointLocator::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
@@ -18,12 +18,15 @@

class QgsPointXY;
class QgsVectorLayer;
class QgsFeatureRenderer;
class QgsRenderContext;

#include "qgis_core.h"
#include "qgsfeature.h"
#include "qgspointxy.h"
#include "qgscoordinatereferencesystem.h"
#include "qgscoordinatetransform.h"
#include <memory>

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

/**
* Configure render context - if not null, it will use to index only visible feature
* \since QGIS 3.2
*/
void setRenderContext( const QgsRenderContext &context );

/**
* The type of a snap result or the filter type for a snap request.
*/
@@ -251,8 +260,6 @@ class CORE_EXPORT QgsPointLocator : public QObject
//! find out if the point is in any polygons
MatchList pointInPolygon( const QgsPointXY &point );

//

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

QgsFeatureIds mFeatureIds;

//! R-tree containing spatial index
QgsCoordinateTransform mTransform;
QgsVectorLayer *mLayer = nullptr;
QgsRectangle *mExtent = nullptr;

std::unique_ptr<QgsRenderContext> mContext;

friend class QgsPointLocator_VisitorNearestVertex;
friend class QgsPointLocator_VisitorNearestEdge;
friend class QgsPointLocator_VisitorArea;
@@ -19,10 +19,12 @@
#include "qgsproject.h"
#include "qgsvectorlayer.h"
#include "qgslogger.h"
#include "qgsrenderer.h"

QgsSnappingUtils::QgsSnappingUtils( QObject *parent )
QgsSnappingUtils::QgsSnappingUtils( QObject *parent, bool enableSnappingForInvisibleFeature )
: QObject( parent )
, mSnappingConfig( QgsProject::instance() )
, mEnableSnappingForInvisibleFeature( enableSnappingForInvisibleFeature )
{
}

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


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


static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointLocator::Match &candidateMatch, double maxDistance )
{

// is candidate match relevant?
if ( !candidateMatch.isValid() || candidateMatch.distance() > maxDistance )
return;
@@ -174,7 +175,6 @@ static void _replaceIfBetter( QgsPointLocator::Match &bestMatch, const QgsPointL
bestMatch = candidateMatch; // the other match is better!
}


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


void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers )
{
if ( mIsIndexing )
@@ -341,6 +340,7 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
Q_FOREACH ( const LayerAndAreaOfInterest &entry, layers )
{
QgsVectorLayer *vl = entry.first;

if ( vl->geometryType() == QgsWkbTypes::NullGeometry || mStrategy == IndexNeverFull )
continue;

@@ -359,7 +359,12 @@ void QgsSnappingUtils::prepareIndex( const QList<LayerAndAreaOfInterest> &layers
QgsVectorLayer *vl = entry.first;
QTime tt;
tt.start();

QgsPointLocator *loc = locatorForLayer( vl );

if ( !mEnableSnappingForInvisibleFeature )
loc->setRenderContext( QgsRenderContext::fromMapSettings( mMapSettings ) );

if ( mStrategy == IndexExtent )
{
QgsRectangle rect( mMapSettings.extent() );
@@ -428,6 +433,11 @@ QgsSnappingConfig QgsSnappingUtils::config() const
return mSnappingConfig;
}

void QgsSnappingUtils::setEnableSnappingForInvisibleFeature( bool enable )
{
mEnableSnappingForInvisibleFeature = enable;
}

void QgsSnappingUtils::setConfig( const QgsSnappingConfig &config )
{
if ( mSnappingConfig == config )
@@ -53,7 +53,7 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
public:

//! Constructor for QgsSnappingUtils
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr );
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true );
~QgsSnappingUtils() override;

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

/**
* Set if invisible features must be snapped or not.
*
* \param enable Enable or not this feature
*
* \since QGIS 3.2
*/
void setEnableSnappingForInvisibleFeature( bool enable );

public slots:

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

//! internal flag that an indexing process is going on. Prevents starting two processes in parallel.
bool mIsIndexing = false;

//! Disable or not the snapping on all features. By default is always true except for non visible features on map canvas.
bool mEnableSnappingForInvisibleFeature = true;

};


0 comments on commit 66b0e59

Please sign in to comment.
You can’t perform that action at this time.