Skip to content

Commit 1a5a7c5

Browse files
author
Hugo Mercier
committed
[Feature] Data dependencies between layers
This allows to declare data dependencies between layers. A data dependency occurs when a data modification in a layer, not by direct user manipulation may modify data of other layers. This is the case for instance when geometry of a layer is updated by a database trigger after modification of another layer's geometry.
1 parent e6fd2e2 commit 1a5a7c5

15 files changed

+773
-198
lines changed

images/images.qrc

+1
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,7 @@
665665
<file>themes/default/mActionAddAfsLayer.svg</file>
666666
<file>themes/default/mIconFormSelect.svg</file>
667667
<file>themes/default/mActionMultiEdit.svg</file>
668+
<file>themes/default/dependencies.svg</file>
668669
</qresource>
669670
<qresource prefix="/images/tips">
670671
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
+151
Loading

python/core/qgsmaplayer.sip

+20
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,23 @@ class QgsMapLayer : QObject
676676
*/
677677
void emitStyleChanged();
678678

679+
/**
680+
* Sets the list of layers that may modify data/geometries of this layer when modified.
681+
* @see dataDependencies
682+
*
683+
* @param layersIds IDs of the layers that this layer depends on
684+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
685+
*/
686+
virtual bool setDataDependencies( const QSet<QString>& layersIds );
687+
688+
/**
689+
* Gets the list of layers that may modify data/geometries of this layer when modified.
690+
* @see setDataDependencies
691+
*
692+
* @returns IDs of the layers that this layer depends on
693+
*/
694+
virtual QSet<QString> dataDependencies() const;
695+
679696
signals:
680697

681698
/** Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar) */
@@ -766,4 +783,7 @@ class QgsMapLayer : QObject
766783
void appendError( const QgsErrorMessage &error );
767784
/** Set error message */
768785
void setError( const QgsError &error );
786+
787+
//! Checks if new change dependency candidates introduce a cycle
788+
bool hasDataDependencyCycle( const QSet<QString>& layersIds ) const;
769789
};

python/core/qgsvectorlayer.sip

+21-1
Original file line numberDiff line numberDiff line change
@@ -422,10 +422,30 @@ class QgsVectorLayer : QgsMapLayer
422422
const QList<QgsVectorJoinInfo> vectorJoins() const;
423423

424424
/**
425-
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
425+
* Gets the list of layer ids on which this layer depends, as returned by the provider.
426+
* This in particular determines the order of layer loading.
426427
*/
427428
virtual QSet<QString> layerDependencies() const;
428429

430+
/**
431+
* Sets the list of layers that may modify data/geometries of this layer when modified.
432+
* This is meant mainly to declare database triggers between layers.
433+
* When one of these layers is modified (feature added/deleted or geometry changed),
434+
* dataChanged() will be emitted, allowing users of this layer to refresh / update it.
435+
*
436+
* @param layersIds IDs of the layers that this layer depends on
437+
* @returns false if a dependency cycle has been detected (the change dependency set is not changed in that case)
438+
*/
439+
bool setDataDependencies( const QSet<QString>& layersIds );
440+
441+
/**
442+
* Gets the list of layers that may modify data/geometries of this layer when modified.
443+
* @see setDataDependencies
444+
*
445+
* @returns IDs of the layers that this layer depends on
446+
*/
447+
QSet<QString> dataDependencies() const;
448+
429449
/**
430450
* Add a new field which is calculated by the expression specified
431451
*

src/app/qgsvectorlayerproperties.cpp

+30
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#include "qgsdatasourceuri.h"
5353
#include "qgsrenderer.h"
5454
#include "qgsexpressioncontext.h"
55+
#include "layertree/qgslayertreelayer.h"
5556

5657
#include <QMessageBox>
5758
#include <QDir>
@@ -291,6 +292,23 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
291292

292293
QString title = QString( tr( "Layer Properties - %1" ) ).arg( mLayer->name() );
293294
restoreOptionsBaseUi( title );
295+
296+
mLayersDependenciesTreeGroup.reset( QgsProject::instance()->layerTreeRoot()->clone() );
297+
QgsLayerTreeLayer* layer = mLayersDependenciesTreeGroup->findLayer( mLayer->id() );
298+
layer->parent()->takeChild( layer );
299+
mLayersDependenciesTreeModel.reset( new QgsLayerTreeModel( mLayersDependenciesTreeGroup.data() ) );
300+
// use visibility as selection
301+
mLayersDependenciesTreeModel->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
302+
303+
mLayersDependenciesTreeGroup->setVisible( Qt::Unchecked );
304+
305+
QSet<QString> dependencySources = mLayer->dataDependencies();
306+
Q_FOREACH ( QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
307+
{
308+
layer->setVisible( dependencySources.contains( layer->layerId() ) ? Qt::Checked : Qt::Unchecked );
309+
}
310+
311+
mLayersDependenciesTreeView->setModel( mLayersDependenciesTreeModel.data() );
294312
} // QgsVectorLayerProperties ctor
295313

296314

@@ -558,6 +576,18 @@ void QgsVectorLayerProperties::apply()
558576
QgsExpressionContextUtils::setLayerVariables( mLayer, mVariableEditor->variablesInActiveScope() );
559577
updateVariableEditor();
560578

579+
// save layer dependencies
580+
QSet<QString> deps;
581+
Q_FOREACH ( const QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
582+
{
583+
if ( layer->isVisible() )
584+
deps << layer->layerId();
585+
}
586+
if ( ! mLayer->setDataDependencies( deps ) )
587+
{
588+
QMessageBox::warning( nullptr, tr( "Dependency cycle" ), tr( "This configuration introduces a cycle in data dependencies and will be ignored" ) );
589+
}
590+
561591
// update symbology
562592
emit refreshLegend( mLayer->id() );
563593

src/app/qgsvectorlayerproperties.h

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "qgscontexthelp.h"
2626
#include "qgsmaplayerstylemanager.h"
2727
#include "qgsvectorlayer.h"
28+
#include "layertree/qgslayertreemodel.h"
29+
#include "layertree/qgslayertreegroup.h"
2830

2931
class QgsMapLayer;
3032

@@ -193,6 +195,9 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private
193195

194196
QgsExpressionContext createExpressionContext() const override;
195197

198+
QScopedPointer<QgsLayerTreeGroup> mLayersDependenciesTreeGroup;
199+
QScopedPointer<QgsLayerTreeModel> mLayersDependenciesTreeModel;
200+
196201
private slots:
197202
void openPanel( QgsPanelWidget* panel );
198203
};

src/core/qgslayerdefinition.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
111111
joinNode.toElement().setAttribute( "joinLayerId", newid );
112112
}
113113
}
114+
115+
// change IDs of dependencies
116+
QDomNodeList dataDeps = doc.elementsByTagName( "dataDependencies" );
117+
for ( int i = 0; i < dataDeps.size(); i++ )
118+
{
119+
QDomNodeList layers = dataDeps.at( i ).childNodes();
120+
for ( int j = 0; j < layers.size(); j++ )
121+
{
122+
QDomElement elt = layers.at( j ).toElement();
123+
if ( elt.attribute( "id" ) == oldid )
124+
{
125+
elt.setAttribute( "id", newid );
126+
}
127+
}
128+
}
129+
114130
}
115131

116132
QDomElement layerTreeElem = doc.documentElement().firstChildElement( "layer-tree-group" );

src/core/qgsmaplayer.cpp

+59
Original file line numberDiff line numberDiff line change
@@ -1680,3 +1680,62 @@ void QgsMapLayer::setExtent( const QgsRectangle &r )
16801680
{
16811681
mExtent = r;
16821682
}
1683+
1684+
static QList<const QgsMapLayer*> _depOutEdges( const QgsMapLayer* vl, const QgsMapLayer* that, const QSet<QString>& layersIds )
1685+
{
1686+
QList<const QgsMapLayer*> lst;
1687+
if ( vl == that )
1688+
{
1689+
Q_FOREACH ( QString layerId, layersIds )
1690+
{
1691+
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
1692+
lst << l;
1693+
}
1694+
}
1695+
else
1696+
{
1697+
Q_FOREACH ( QString layerId, vl->dataDependencies() )
1698+
{
1699+
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
1700+
lst << l;
1701+
}
1702+
}
1703+
return lst;
1704+
}
1705+
1706+
static bool _depHasCycleDFS( const QgsMapLayer* n, QHash<const QgsMapLayer*, int>& mark, const QgsMapLayer* that, const QSet<QString>& layersIds )
1707+
{
1708+
if ( mark.value( n ) == 1 ) // temporary
1709+
return true;
1710+
if ( mark.value( n ) == 0 ) // not visited
1711+
{
1712+
mark[n] = 1; // temporary
1713+
Q_FOREACH ( const QgsMapLayer* m, _depOutEdges( n, that, layersIds ) )
1714+
{
1715+
if ( _depHasCycleDFS( m, mark, that, layersIds ) )
1716+
return true;
1717+
}
1718+
mark[n] = 2; // permanent
1719+
}
1720+
return false;
1721+
}
1722+
1723+
bool QgsMapLayer::hasDataDependencyCycle( const QSet<QString>& layersIds ) const
1724+
{
1725+
QHash<const QgsMapLayer*, int> marks;
1726+
return _depHasCycleDFS( this, marks, this, layersIds );
1727+
}
1728+
1729+
bool QgsMapLayer::setDataDependencies( const QSet<QString>& layersIds )
1730+
{
1731+
if ( hasDataDependencyCycle( layersIds ) )
1732+
return false;
1733+
1734+
mDataDependencies = layersIds;
1735+
return true;
1736+
}
1737+
1738+
QSet<QString> QgsMapLayer::dataDependencies() const
1739+
{
1740+
return mDataDependencies;
1741+
}

0 commit comments

Comments
 (0)