Skip to content
Permalink
Browse files

Merge pull request #8005 from lbartoletti/bugfix_18902

Fixes #18902 Snapping and tracing inconsistent behaviour
  • Loading branch information
pblottiere committed Nov 19, 2018
2 parents 357ea19 + 53532fa commit 50e2b76ae73b8d4507940eea046d19a7b515f71e
@@ -53,6 +53,13 @@ Returns the CRS used for tracing.
Sets the ``crs`` and transform ``context`` used for tracing.

.. seealso:: :py:func:`destinationCrs`
%End

void setRenderContext( const QgsRenderContext *renderContext );
%Docstring
Sets the ``renderContext`` used for tracing only on visible features.

.. versionadded:: 3.4
%End

QgsRectangle extent() const;
@@ -23,6 +23,8 @@
#include "qgslogger.h"
#include "qgsvectorlayer.h"
#include "qgsexception.h"
#include "qgsrenderer.h"
#include "qgssettings.h"

#include <queue>
#include <vector>
@@ -473,10 +475,30 @@ bool QgsTracer::initGraph()

t1.start();
int featuresCounted = 0;
for ( QgsVectorLayer *vl : qgis::as_const( mLayers ) )
bool enableInvisibleFeature = QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool();
for ( const QgsVectorLayer *vl : qgis::as_const( mLayers ) )
{
QgsFeatureRequest request;
request.setNoAttributes();
bool filter = false;
std::unique_ptr< QgsFeatureRenderer > renderer;
std::unique_ptr<QgsRenderContext> ctx;

if ( !enableInvisibleFeature && mRenderContext && vl->renderer() )
{
renderer.reset( vl->renderer()->clone() );
ctx.reset( new QgsRenderContext( *mRenderContext.get() ) );
ctx->expressionContext() << QgsExpressionContextUtils::layerScope( vl );

// setup scale for scale dependent visibility (rule based)
renderer->startRender( *ctx.get(), vl->fields() );
filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
request.setSubsetOfAttributes( renderer->usedAttributes( *ctx.get() ), vl->fields() );
}
else
{
request.setNoAttributes();
}

request.setDestinationCrs( mCRS, mTransformContext );
if ( !mExtent.isEmpty() )
request.setFilterRect( mExtent );
@@ -487,12 +509,26 @@ bool QgsTracer::initGraph()
if ( !f.hasGeometry() )
continue;

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

extractLinework( f.geometry(), mpl );

++featuresCounted;
if ( mMaxFeatureCount != 0 && featuresCounted >= mMaxFeatureCount )
return false;
}

if ( renderer )
{
renderer->stopRender( *ctx.get() );
}
}
int timeExtract = t1.elapsed();

@@ -544,6 +580,7 @@ bool QgsTracer::initGraph()
Q_UNUSED( timeMake );
QgsDebugMsg( QStringLiteral( "tracer extract %1 ms, noding %2 ms (call %3 ms), make %4 ms" )
.arg( timeExtract ).arg( timeNoding ).arg( timeNodingCall ).arg( timeMake ) );

return true;
}

@@ -562,6 +599,9 @@ void QgsTracer::setLayers( const QList<QgsVectorLayer *> &layers )
disconnect( layer, &QgsVectorLayer::featureAdded, this, &QgsTracer::onFeatureAdded );
disconnect( layer, &QgsVectorLayer::featureDeleted, this, &QgsTracer::onFeatureDeleted );
disconnect( layer, &QgsVectorLayer::geometryChanged, this, &QgsTracer::onGeometryChanged );
disconnect( layer, &QgsVectorLayer::attributeValueChanged, this, &QgsTracer::onAttributeValueChanged );
disconnect( layer, &QgsVectorLayer::dataChanged, this, &QgsTracer::onDataChanged );
disconnect( layer, &QgsVectorLayer::styleChanged, this, &QgsTracer::onStyleChanged );
disconnect( layer, &QObject::destroyed, this, &QgsTracer::onLayerDestroyed );
}

@@ -572,6 +612,9 @@ void QgsTracer::setLayers( const QList<QgsVectorLayer *> &layers )
connect( layer, &QgsVectorLayer::featureAdded, this, &QgsTracer::onFeatureAdded );
connect( layer, &QgsVectorLayer::featureDeleted, this, &QgsTracer::onFeatureDeleted );
connect( layer, &QgsVectorLayer::geometryChanged, this, &QgsTracer::onGeometryChanged );
connect( layer, &QgsVectorLayer::attributeValueChanged, this, &QgsTracer::onAttributeValueChanged );
connect( layer, &QgsVectorLayer::dataChanged, this, &QgsTracer::onDataChanged );
connect( layer, &QgsVectorLayer::styleChanged, this, &QgsTracer::onStyleChanged );
connect( layer, &QObject::destroyed, this, &QgsTracer::onLayerDestroyed );
}

@@ -585,6 +628,12 @@ void QgsTracer::setDestinationCrs( const QgsCoordinateReferenceSystem &crs, cons
invalidateGraph();
}

void QgsTracer::setRenderContext( const QgsRenderContext *renderContext )
{
mRenderContext.reset( new QgsRenderContext( *renderContext ) );
invalidateGraph();
}

void QgsTracer::setExtent( const QgsRectangle &extent )
{
if ( mExtent == extent )
@@ -649,6 +698,24 @@ void QgsTracer::onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
invalidateGraph();
}

void QgsTracer::onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value )
{
Q_UNUSED( fid );
Q_UNUSED( idx );
Q_UNUSED( value );
invalidateGraph();
}

void QgsTracer::onDataChanged( )
{
invalidateGraph();
}

void QgsTracer::onStyleChanged( )
{
invalidateGraph();
}

void QgsTracer::onLayerDestroyed( QObject *obj )
{
// remove the layer before it is completely invalid (static_cast should be the safest cast)
@@ -1,17 +1,17 @@
/***************************************************************************
qgstracer.h
--------------------------------------
Date : January 2016
Copyright : (C) 2016 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
qgstracer.h
--------------------------------------
Date : January 2016
Copyright : (C) 2016 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSTRACER_H
#define QGSTRACER_H
@@ -29,6 +29,8 @@ class QgsVectorLayer;
#include "qgsgeometry.h"

struct QgsTracerGraph;
class QgsFeatureRenderer;
class QgsRenderContext;

/**
* \ingroup core
@@ -66,6 +68,12 @@ class CORE_EXPORT QgsTracer : public QObject
*/
void setDestinationCrs( const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context );

/**
* Sets the \a renderContext used for tracing only on visible features.
* \since QGIS 3.4
*/
void setRenderContext( const QgsRenderContext *renderContext );

//! Gets extent to which graph's features will be limited (empty extent means no limit)
QgsRectangle extent() const { return mExtent; }
//! Sets extent to which graph's features will be limited (empty extent means no limit)
@@ -160,6 +168,9 @@ class CORE_EXPORT QgsTracer : public QObject
void onFeatureAdded( QgsFeatureId fid );
void onFeatureDeleted( QgsFeatureId fid );
void onGeometryChanged( QgsFeatureId fid, const QgsGeometry &geom );
void onAttributeValueChanged( QgsFeatureId fid, int idx, const QVariant &value );
void onDataChanged( );
void onStyleChanged( );
void onLayerDestroyed( QObject *obj );

private:
@@ -171,6 +182,8 @@ class CORE_EXPORT QgsTracer : public QObject
QgsCoordinateReferenceSystem mCRS;
//! Coordinate transform context
QgsCoordinateTransformContext mTransformContext;
//! Render context
std::unique_ptr<QgsRenderContext> mRenderContext;
//! Extent for graph building (empty extent means no limit)
QgsRectangle mExtent;

@@ -98,6 +98,8 @@ void QgsMapCanvasTracer::reportError( QgsTracer::PathError err, bool addingVerte
void QgsMapCanvasTracer::configure()
{
setDestinationCrs( mCanvas->mapSettings().destinationCrs(), mCanvas->mapSettings().transformContext() );
QgsRenderContext ctx = QgsRenderContext::fromMapSettings( mCanvas->mapSettings() );
setRenderContext( &ctx );
setExtent( mCanvas->extent() );

QList<QgsVectorLayer *> layers;
@@ -20,6 +20,12 @@
#include <qgstracer.h>
#include <qgsvectorlayer.h>
#include "qgsproject.h"
#include "qgscategorizedsymbolrenderer.h"
#include "qgssettings.h"
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgsmapsettings.h"
#include "qgssnappingutils.h"

class TestQgsTracer : public QObject
{
@@ -36,6 +42,7 @@ class TestQgsTracer : public QObject
void testReprojection();
void testCurved();
void testOffset();
void testInvisible();

private:

@@ -156,6 +163,97 @@ void TestQgsTracer::testSimple()
delete vl;
}

void TestQgsTracer::testInvisible()
{
QgsVectorLayer *mVL = new QgsVectorLayer( QStringLiteral( "Linestring?field=fld:int" ), QStringLiteral( "x" ), QStringLiteral( "memory" ) );
QgsFeature f1, f2, f3, f4;
int idx = mVL->fields().indexFromName( QStringLiteral( "fld" ) );
QVERIFY( idx != -1 );
f1.initAttributes( 1 );
f2.initAttributes( 1 );
f3.initAttributes( 1 );
f4.initAttributes( 1 );

/* This shape - nearly a square (one side is shifted to have exactly one shortest
* path between corners):
* 0,10 +----+ 20,10
* | /
* 0,0 +--+ 10,0
*/
QgsGeometry geom = QgsGeometry::fromWkt( "LINESTRING(0 0, 0 10)" );
f1.setGeometry( geom );
f1.setAttribute( idx, QVariant( 2 ) );
geom = QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" );
f2.setGeometry( geom );
f2.setAttribute( idx, QVariant( 1 ) );
geom = QgsGeometry::fromWkt( "LINESTRING(0 10, 20 10)" );
f3.setGeometry( geom );
f3.setAttribute( idx, QVariant( 1 ) );
geom = QgsGeometry::fromWkt( "LINESTRING(10 0, 20 10)" );
f4.setGeometry( geom );
f4.setAttribute( idx, QVariant( 1 ) );
QgsFeatureList flist;
flist << f1 << f2 << f3 << f4;

mVL->dataProvider()->addFeatures( flist );

QgsProject::instance()->addMapLayer( mVL );

QgsCategorizedSymbolRenderer *renderer = new QgsCategorizedSymbolRenderer();
renderer->setClassAttribute( QStringLiteral( "fld" ) );
renderer->setSourceSymbol( QgsSymbol::defaultSymbol( QgsWkbTypes::LineGeometry ) );
renderer->addCategory( QgsRendererCategory( "2", QgsSymbol::defaultSymbol( QgsWkbTypes::LineGeometry ), QStringLiteral( "2" ) ) );
mVL->setRenderer( renderer );

//create legend with symbology nodes for categorized renderer
QgsLayerTree *root = new QgsLayerTree();
QgsLayerTreeLayer *n = new QgsLayerTreeLayer( mVL );
root->addChildNode( n );
QgsLayerTreeModel *m = new QgsLayerTreeModel( root, nullptr );
m->refreshLayerLegend( n );

QList<QgsLayerTreeModelLegendNode *> nodes = m->layerLegendNodes( n );
QCOMPARE( nodes.length(), 1 );
//uncheck all and test that all nodes are unchecked
static_cast< QgsSymbolLegendNode * >( nodes.at( 0 ) )->uncheckAllItems();
Q_FOREACH ( QgsLayerTreeModelLegendNode *ln, nodes )
{
QVERIFY( ln->data( Qt::CheckStateRole ) == Qt::Unchecked );
}

QgsMapSettings mapSettings;
mapSettings.setOutputSize( QSize( 100, 100 ) );
mapSettings.setExtent( QgsRectangle( 0, 0, 1, 1 ) );
QVERIFY( mapSettings.hasValidSettings() );
mapSettings.setLayers( QList<QgsMapLayer *>() << mVL );

QgsSnappingUtils u;
u.setMapSettings( mapSettings );
u.setEnableSnappingForInvisibleFeature( false );
u.setCurrentLayer( mVL );

QgsSnappingConfig snappingConfig = u.config();
snappingConfig.setEnabled( true );
snappingConfig.setTolerance( 10 );
snappingConfig.setUnits( QgsTolerance::Pixels );
snappingConfig.setMode( QgsSnappingConfig::ActiveLayer );
u.setConfig( snappingConfig );
QgsTracer tracer;
tracer.setLayers( QList<QgsVectorLayer *>() << mVL );

QgsPolylineXY points1 = tracer.findShortestPath( QgsPointXY( 10, 0 ), QgsPointXY( 0, 10 ) );
QCOMPARE( points1.count(), 3 );
QCOMPARE( points1[0], QgsPointXY( 10, 0 ) );
QCOMPARE( points1[1], QgsPointXY( 0, 0 ) );
QCOMPARE( points1[2], QgsPointXY( 0, 10 ) );

QgsRenderContext renderContext = QgsRenderContext::fromMapSettings( mapSettings );
tracer.setRenderContext( &renderContext );
points1 = tracer.findShortestPath( QgsPointXY( 10, 0 ), QgsPointXY( 0, 10 ) );
QCOMPARE( points1.count(), 0 );

}

void TestQgsTracer::testPolygon()
{
// the same shape as in testSimple() but with just one polygon ring

0 comments on commit 50e2b76

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