Rework layer dependencies

A new class QgsMapLayerDependency allows to represent different kinds
of dependencies between layers.
This commit is contained in:
Hugo Mercier 2016-08-31 09:18:45 +02:00
parent 1a5a7c5905
commit 0749ba43ba
19 changed files with 243 additions and 85 deletions

View File

@ -9,6 +9,7 @@ PyQgsSipCoverage
PyQgsSpatialiteProvider
PyQgsVirtualLayerDefinition
PyQgsVirtualLayerProvider
PyQgsLayerDependencies
qgis_composermapgridtest
qgis_composerutils
ProcessingGrass7AlgorithmsImageryTest

View File

@ -80,6 +80,7 @@
%Include qgslogger.sip
%Include qgsmaphittest.sip
%Include qgsmaplayer.sip
%Include qgsmaplayerdependency.sip
%Include qgsmaplayerlegend.sip
%Include qgsmaplayermodel.sip
%Include qgsmaplayerproxymodel.sip

View File

@ -678,7 +678,7 @@ class QgsMapLayer : QObject
/**
* Sets the list of layers that may modify data/geometries of this layer when modified.
* @see dataDependencies
* @see dependencies()
*
* @param layersIds IDs of the layers that this layer depends on
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
@ -686,12 +686,21 @@ class QgsMapLayer : QObject
virtual bool setDataDependencies( const QSet<QString>& layersIds );
/**
* Gets the list of layers that may modify data/geometries of this layer when modified.
* @see setDataDependencies
* Sets the list of layers that may modify data/geometries of this layer when modified.
* @see dependencies()
*
* @returns IDs of the layers that this layer depends on
* @param set of QgsMapLayerDependency. Only user-defined dependencies will be added
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
*/
virtual QSet<QString> dataDependencies() const;
bool setDataDependencies( const QSet<QgsMapLayerDependency>& layers );
/**
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
* as well as dependencies given by the provider
*
* @returns a set of QgsMapLayerDependency
*/
virtual QSet<QgsMapLayerDependency> dependencies() const;
signals:
@ -785,5 +794,5 @@ class QgsMapLayer : QObject
void setError( const QgsError &error );
//! Checks if new change dependency candidates introduce a cycle
bool hasDataDependencyCycle( const QSet<QString>& layersIds ) const;
bool hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layersIds ) const;
};

View File

@ -0,0 +1,36 @@
class QgsMapLayerDependency
{
%TypeHeaderCode
#include "qgsmaplayerdependency.h"
%End
public:
//! Type of dependency
enum Type
{
PresenceDependency = 1, // The layer must be already present (in the registry) for this dependency to be resolved
DataDependency = 2 // The layer may be invalidated by data changes on another layer
};
//! Origin of the dependency
enum Origin
{
FromProvider = 0, // Dependency given by the provider, the user cannot change it
FromUser = 1 // Dependency given by the user
};
//! Standard constructor
QgsMapLayerDependency( QString layerId, Type type = DataDependency, Origin origin = FromUser );
//! Return the dependency type
Type type() const;
//! Return the dependency origin
Origin origin() const;
//! Return the ID of the layer this dependency depends on
QString layerId() const;
bool operator==( const QgsMapLayerDependency& other ) const;
};

View File

@ -368,7 +368,7 @@ class QgsVectorDataProvider : QgsDataProvider
/**
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
*/
virtual QSet<QString> layerDependencies() const;
virtual QSet<QgsMapLayerDependency> dependencies() const;
signals:
/** Signals an error in this provider */

View File

@ -421,12 +421,6 @@ class QgsVectorLayer : QgsMapLayer
const QList<QgsVectorJoinInfo> vectorJoins() const;
/**
* Gets the list of layer ids on which this layer depends, as returned by the provider.
* This in particular determines the order of layer loading.
*/
virtual QSet<QString> layerDependencies() const;
/**
* Sets the list of layers that may modify data/geometries of this layer when modified.
* This is meant mainly to declare database triggers between layers.
@ -439,12 +433,12 @@ class QgsVectorLayer : QgsMapLayer
bool setDataDependencies( const QSet<QString>& layersIds );
/**
* Gets the list of layers that may modify data/geometries of this layer when modified.
* @see setDataDependencies
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
* as well as dependencies given by the provider
*
* @returns IDs of the layers that this layer depends on
* @returns a set of QgsMapLayerDependency
*/
QSet<QString> dataDependencies() const;
virtual QSet<QgsMapLayerDependency> dependencies() const;
/**
* Add a new field which is calculated by the expression specified

View File

@ -302,7 +302,11 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
mLayersDependenciesTreeGroup->setVisible( Qt::Unchecked );
QSet<QString> dependencySources = mLayer->dataDependencies();
QSet<QString> dependencySources;
Q_FOREACH ( const QgsMapLayerDependency& dep, mLayer->dependencies() )
{
dependencySources << dep.layerId();
}
Q_FOREACH ( QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
{
layer->setVisible( dependencySources.contains( layer->layerId() ) ? Qt::Checked : Qt::Unchecked );

View File

@ -1681,38 +1681,38 @@ void QgsMapLayer::setExtent( const QgsRectangle &r )
mExtent = r;
}
static QList<const QgsMapLayer*> _depOutEdges( const QgsMapLayer* vl, const QgsMapLayer* that, const QSet<QString>& layersIds )
static QList<const QgsMapLayer*> _depOutEdges( const QgsMapLayer* vl, const QgsMapLayer* that, const QSet<QgsMapLayerDependency>& layers )
{
QList<const QgsMapLayer*> lst;
if ( vl == that )
{
Q_FOREACH ( QString layerId, layersIds )
Q_FOREACH ( const QgsMapLayerDependency& dep, layers )
{
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) )
lst << l;
}
}
else
{
Q_FOREACH ( QString layerId, vl->dataDependencies() )
Q_FOREACH ( const QgsMapLayerDependency& dep, vl->dependencies() )
{
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) )
lst << l;
}
}
return lst;
}
static bool _depHasCycleDFS( const QgsMapLayer* n, QHash<const QgsMapLayer*, int>& mark, const QgsMapLayer* that, const QSet<QString>& layersIds )
static bool _depHasCycleDFS( const QgsMapLayer* n, QHash<const QgsMapLayer*, int>& mark, const QgsMapLayer* that, const QSet<QgsMapLayerDependency>& layers )
{
if ( mark.value( n ) == 1 ) // temporary
return true;
if ( mark.value( n ) == 0 ) // not visited
{
mark[n] = 1; // temporary
Q_FOREACH ( const QgsMapLayer* m, _depOutEdges( n, that, layersIds ) )
Q_FOREACH ( const QgsMapLayer* m, _depOutEdges( n, that, layers ) )
{
if ( _depHasCycleDFS( m, mark, that, layersIds ) )
if ( _depHasCycleDFS( m, mark, that, layers ) )
return true;
}
mark[n] = 2; // permanent
@ -1720,22 +1720,38 @@ static bool _depHasCycleDFS( const QgsMapLayer* n, QHash<const QgsMapLayer*, int
return false;
}
bool QgsMapLayer::hasDataDependencyCycle( const QSet<QString>& layersIds ) const
bool QgsMapLayer::hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layers ) const
{
QHash<const QgsMapLayer*, int> marks;
return _depHasCycleDFS( this, marks, this, layersIds );
return _depHasCycleDFS( this, marks, this, layers );
}
QSet<QgsMapLayerDependency> QgsMapLayer::dependencies() const
{
return mDataDependencies;
}
bool QgsMapLayer::setDataDependencies( const QSet<QString>& layersIds )
{
if ( hasDataDependencyCycle( layersIds ) )
QSet<QgsMapLayerDependency> deps;
Q_FOREACH ( QString layerId, layersIds )
{
deps << QgsMapLayerDependency( layerId );
}
if ( hasDataDependencyCycle( deps ) )
return false;
mDataDependencies = layersIds;
mDataDependencies = deps;
return true;
}
QSet<QString> QgsMapLayer::dataDependencies() const
bool QgsMapLayer::setDataDependencies( const QSet<QgsMapLayerDependency>& layers )
{
return mDataDependencies;
QSet<QString> deps;
Q_FOREACH ( const QgsMapLayerDependency& dep, layers )
{
if ( dep.origin() == QgsMapLayerDependency::FromUser && dep.type() == QgsMapLayerDependency::DataDependency )
deps << dep.layerId();
}
return setDataDependencies( deps );
}

View File

@ -32,6 +32,7 @@
#include "qgsrectangle.h"
#include "qgscoordinatereferencesystem.h"
#include "qgsrendercontext.h"
#include "qgsmaplayerdependency.h"
class QgsMapLayerLegend;
class QgsMapLayerRenderer;
@ -700,7 +701,7 @@ class CORE_EXPORT QgsMapLayer : public QObject
/**
* Sets the list of layers that may modify data/geometries of this layer when modified.
* @see dataDependencies
* @see dependencies()
*
* @param layersIds IDs of the layers that this layer depends on
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
@ -708,12 +709,21 @@ class CORE_EXPORT QgsMapLayer : public QObject
virtual bool setDataDependencies( const QSet<QString>& layersIds );
/**
* Gets the list of layers that may modify data/geometries of this layer when modified.
* @see setDataDependencies
* Sets the list of layers that may modify data/geometries of this layer when modified.
* @see dependencies()
*
* @returns IDs of the layers that this layer depends on
* @param layers set of QgsMapLayerDependency. Only user-defined dependencies will be added
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
*/
virtual QSet<QString> dataDependencies() const;
bool setDataDependencies( const QSet<QgsMapLayerDependency>& layers );
/**
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
* as well as dependencies given by the provider
*
* @returns a set of QgsMapLayerDependency
*/
virtual QSet<QgsMapLayerDependency> dependencies() const;
signals:
@ -854,10 +864,10 @@ class CORE_EXPORT QgsMapLayer : public QObject
QgsError mError;
//! List of layers that may modify this layer on modification
QSet<QString> mDataDependencies;
QSet<QgsMapLayerDependency> mDataDependencies;
//! Checks whether a new set of data dependencies will introduce a cycle
bool hasDataDependencyCycle( const QSet<QString>& layersIds ) const;
bool hasDataDependencyCycle( const QSet<QgsMapLayerDependency>& layers ) const;
private:
/**

View File

@ -0,0 +1,85 @@
/***************************************************************************
qgsmaplayerdependency.h - description
-------------------
begin : September 2016
copyright : (C) 2016 by Hugo Mercier
email : hugo dot mercier at oslandia 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 QGSMAPLAYERDEPENDENCY_H
#define QGSMAPLAYERDEPENDENCY_H
#include <QString>
/** \ingroup core
* This class models dependencies with or between map layers.
* A dependency is defined by a layer ID, a type and an origin.
* The two combinations of type/origin that are currently supported are:
* - PresenceDependency && FromProvider: virtual layers for instance which may depend on other layers already loaded to work
* - DataDependency && FromUser: dependencies given by the user, mainly to represent database triggers
*
* @note added in QGIS 3.0
*/
class CORE_EXPORT QgsMapLayerDependency
{
public:
//! Type of dependency
enum Type
{
PresenceDependency = 1, //< The layer must be already present (in the registry) for this dependency to be resolved
DataDependency = 2 //< The layer may be invalidated by data changes on another layer
};
//! Origin of the dependency
enum Origin
{
FromProvider = 0, //< Dependency given by the provider, the user cannot change it
FromUser = 1 //< Dependency given by the user
};
//! Standard constructor
QgsMapLayerDependency( QString layerId, Type type = DataDependency, Origin origin = FromUser ) :
mType( type ),
mOrigin( origin ),
mLayerId( layerId )
{}
//! Return the dependency type
Type type() const { return mType; }
//! Return the dependency origin
Origin origin() const { return mOrigin; }
//! Return the ID of the layer this dependency depends on
QString layerId() const { return mLayerId; }
//! Comparison operator
bool operator==( const QgsMapLayerDependency& other ) const
{
return layerId() == other.layerId() && origin() == other.origin() && type() == other.type();
}
private:
Type mType;
Origin mOrigin;
QString mLayerId;
};
/**
* global qHash function for QgsMapLayerDependency, so that it can be used in a QSet
*/
inline uint qHash( const QgsMapLayerDependency& dep )
{
return qHash( dep.layerId() ) + dep.origin() + dep.type();
}
#endif

View File

@ -893,7 +893,7 @@ bool QgsProject::read()
QMap<QString, QgsMapLayer*> existingMaps = QgsMapLayerRegistry::instance()->mapLayers();
for ( QMap<QString, QgsMapLayer*>::iterator it = existingMaps.begin(); it != existingMaps.end(); it++ )
{
it.value()->setDataDependencies( it.value()->dataDependencies() );
it.value()->setDataDependencies( it.value()->dependencies() );
}
// read the project: used by map canvas and legend
@ -997,7 +997,7 @@ void QgsProject::onMapLayersAdded( const QList<QgsMapLayer*>& layers )
// check if we have to update connections for layers with dependencies
for ( QMap<QString, QgsMapLayer*>::iterator it = existingMaps.begin(); it != existingMaps.end(); it++ )
{
QSet<QString> deps = it.value()->dataDependencies();
QSet<QgsMapLayerDependency> deps = it.value()->dependencies();
if ( deps.contains( layer->id() ) )
{
// reconnect to change signals

View File

@ -613,9 +613,9 @@ void QgsVectorDataProvider::pushError( const QString& msg )
emit raiseError( msg );
}
QSet<QString> QgsVectorDataProvider::layerDependencies() const
QSet<QgsMapLayerDependency> QgsVectorDataProvider::dependencies() const
{
return QSet<QString>();
return QSet<QgsMapLayerDependency>();
}
QgsGeometry* QgsVectorDataProvider::convertToProviderType( const QgsGeometry& geom ) const

View File

@ -27,6 +27,7 @@ class QTextCodec;
#include "qgsdataprovider.h"
#include "qgsfeature.h"
#include "qgsaggregatecalculator.h"
#include "qgsmaplayerdependency.h"
typedef QList<int> QgsAttributeList;
typedef QSet<int> QgsAttributeIds;
@ -428,7 +429,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
/**
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
*/
virtual QSet<QString> layerDependencies() const;
virtual QSet<QgsMapLayerDependency> dependencies() const;
signals:
/** Signals an error in this provider */

View File

@ -1639,10 +1639,12 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
// dependencies
QDomElement dependenciesElement = document.createElement( "layerDependencies" );
Q_FOREACH ( QString layerId, layerDependencies() )
Q_FOREACH ( const QgsMapLayerDependency& dep, dependencies() )
{
if ( dep.type() != QgsMapLayerDependency::PresenceDependency )
continue;
QDomElement depElem = document.createElement( "layer" );
depElem.setAttribute( "id", layerId );
depElem.setAttribute( "id", dep.layerId() );
dependenciesElement.appendChild( depElem );
}
layer_node.appendChild( dependenciesElement );
@ -1660,10 +1662,12 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
// change dependencies
QDomElement dataDependenciesElement = document.createElement( "dataDependencies" );
Q_FOREACH ( QString layerId, dataDependencies() )
Q_FOREACH ( const QgsMapLayerDependency& dep, dependencies() )
{
if ( dep.type() != QgsMapLayerDependency::DataDependency )
continue;
QDomElement depElem = document.createElement( "layer" );
depElem.setAttribute( "id", layerId );
depElem.setAttribute( "id", dep.layerId() );
dataDependenciesElement.appendChild( depElem );
}
layer_node.appendChild( dataDependenciesElement );
@ -4085,31 +4089,30 @@ QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &theResultFl
return QgsMapLayer::loadNamedStyle( theURI, theResultFlag );
}
QSet<QString> QgsVectorLayer::layerDependencies() const
QSet<QgsMapLayerDependency> QgsVectorLayer::dependencies() const
{
if ( mDataProvider )
{
return mDataProvider->layerDependencies();
}
return QSet<QString>();
}
QSet<QString> QgsVectorLayer::dataDependencies() const
{
return layerDependencies() + mDataDependencies;
return mDataProvider->dependencies() + mDataDependencies;
return mDataDependencies;
}
bool QgsVectorLayer::setDataDependencies( const QSet<QString>& layersIds )
{
if ( hasDataDependencyCycle( layersIds ) )
QSet<QgsMapLayerDependency> deps;
Q_FOREACH ( QString layerId, layersIds )
{
deps << QgsMapLayerDependency( layerId );
}
if ( hasDataDependencyCycle( deps ) )
return false;
QSet<QString> toAdd = layerDependencies() + layersIds - mDataDependencies;
QSet<QgsMapLayerDependency> toAdd = deps - dependencies();
// disconnect layers that are not present in the list of dependencies anymore
Q_FOREACH ( QString layerId, mDataDependencies )
Q_FOREACH ( const QgsMapLayerDependency& dep, mDataDependencies )
{
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) );
if ( lyr == nullptr )
continue;
disconnect( lyr, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
@ -4119,12 +4122,15 @@ bool QgsVectorLayer::setDataDependencies( const QSet<QString>& layersIds )
}
// assign new dependencies
mDataDependencies = layerDependencies() + layersIds;
if ( mDataProvider )
mDataDependencies = mDataProvider->dependencies() + deps;
else
mDataDependencies = deps;
// connect to new layers
Q_FOREACH ( QString layerId, mDataDependencies )
Q_FOREACH ( const QgsMapLayerDependency& dep, mDataDependencies )
{
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( dep.layerId() ) );
if ( lyr == nullptr )
continue;
connect( lyr, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );

View File

@ -514,12 +514,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
const QList<QgsVectorJoinInfo> vectorJoins() const;
/**
* Gets the list of layer ids on which this layer depends, as returned by the provider.
* This in particular determines the order of layer loading.
*/
virtual QSet<QString> layerDependencies() const;
/**
* Sets the list of layers that may modify data/geometries of this layer when modified.
* This is meant mainly to declare database triggers between layers.
@ -532,12 +526,12 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
bool setDataDependencies( const QSet<QString>& layersIds ) override;
/**
* Gets the list of layers that may modify data/geometries of this layer when modified.
* @see setDataDependencies
* Gets the list of dependencies. This includes data dependencies set by the user (@see setDataDependencies)
* as well as dependencies given by the provider
*
* @returns IDs of the layers that this layer depends on
* @returns a set of QgsMapLayerDependency
*/
QSet<QString> dataDependencies() const override;
virtual QSet<QgsMapLayerDependency> dependencies() const override;
/**
* Add a new field which is calculated by the expression specified

View File

@ -601,13 +601,13 @@ QgsAttributeList QgsVirtualLayerProvider::pkAttributeIndexes() const
return QgsAttributeList();
}
QSet<QString> QgsVirtualLayerProvider::layerDependencies() const
QSet<QgsMapLayerDependency> QgsVirtualLayerProvider::dependencies() const
{
QSet<QString> deps;
QSet<QgsMapLayerDependency> deps;
Q_FOREACH ( const QgsVirtualLayerDefinition::SourceLayer& l, mDefinition.sourceLayers() )
{
if ( l.isReferenced() )
deps << l.reference();
deps << QgsMapLayerDependency( l.reference(), QgsMapLayerDependency::PresenceDependency, QgsMapLayerDependency::FromProvider );
}
return deps;
}

View File

@ -79,7 +79,7 @@ class QgsVirtualLayerProvider: public QgsVectorDataProvider
QgsAttributeList pkAttributeIndexes() const override;
/** Get the list of layer ids on which this layer depends */
QSet<QString> layerDependencies() const override;
QSet<QgsMapLayerDependency> dependencies() const override;
private:

View File

@ -135,6 +135,7 @@ class TestLayerDependencies(unittest.TestCase):
self.assertEqual(l1, 4)
m = u.snapToMap(QPoint(95, 0))
# snapping not updated
self.pointsLayer.setDataDependencies([])
self.assertEqual(m.isValid(), False)
# set layer dependencies
@ -204,7 +205,7 @@ class TestLayerDependencies(unittest.TestCase):
newLinesLayer = l.layer()
self.assertFalse(newPointsLayer is None)
self.assertFalse(newLinesLayer is None)
self.assertTrue(newLinesLayer.id() in newPointsLayer.dataDependencies())
self.assertTrue(newLinesLayer.id() in [dep.layerId() for dep in newPointsLayer.dependencies()])
self.pointsLayer.setDataDependencies([])

View File

@ -677,18 +677,18 @@ class TestQgsVirtualLayerProvider(unittest.TestCase, ProviderTestCase):
self.assertEqual(l2.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l2)
self.assertEqual(len(l2.layerDependencies()), 1)
self.assertEqual(l2.layerDependencies()[0].startswith('france_parts'), True)
self.assertEqual(len(l2.dependencies()), 1)
self.assertEqual(l2.dependencies()[0].layerId().startswith('france_parts'), True)
query = QUrl.toPercentEncoding("SELECT t1.objectid, t2.name_0 FROM france_parts as t1, aa as t2")
l3 = QgsVectorLayer("?query=%s" % query, "bb", "virtual", False)
self.assertEqual(l3.isValid(), True)
QgsMapLayerRegistry.instance().addMapLayer(l3)
self.assertEqual(len(l2.layerDependencies()), 1)
self.assertEqual(l2.layerDependencies()[0].startswith('france_parts'), True)
self.assertEqual(len(l2.dependencies()), 1)
self.assertEqual(l2.dependencies()[0].layerId().startswith('france_parts'), True)
self.assertEqual(len(l3.layerDependencies()), 2)
self.assertEqual(len(l3.dependencies()), 2)
temp = os.path.join(tempfile.gettempdir(), "qgstestproject.qgs")