mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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.
This commit is contained in:
parent
e6fd2e2503
commit
1a5a7c5905
@ -665,6 +665,7 @@
|
||||
<file>themes/default/mActionAddAfsLayer.svg</file>
|
||||
<file>themes/default/mIconFormSelect.svg</file>
|
||||
<file>themes/default/mActionMultiEdit.svg</file>
|
||||
<file>themes/default/dependencies.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/images/tips">
|
||||
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
|
||||
|
151
images/themes/default/dependencies.svg
Normal file
151
images/themes/default/dependencies.svg
Normal file
@ -0,0 +1,151 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="sync_views.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4158">
|
||||
<stop
|
||||
style="stop-color:#0000ff;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop4160" />
|
||||
<stop
|
||||
style="stop-color:#0000a9;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop4162" />
|
||||
</linearGradient>
|
||||
<marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0.0"
|
||||
refX="0.0"
|
||||
id="Arrow2Mend"
|
||||
style="overflow:visible;"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path4171"
|
||||
style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "
|
||||
transform="scale(0.6) rotate(180) translate(0,0)" />
|
||||
</marker>
|
||||
<marker
|
||||
inkscape:stockid="Arrow2Mend"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="Arrow2Mend-8"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path4171-2"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1"
|
||||
d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
|
||||
transform="scale(-0.6,-0.6)" />
|
||||
</marker>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4158"
|
||||
id="radialGradient4178"
|
||||
cx="7.9999766"
|
||||
cy="1040.8622"
|
||||
fx="7.9999766"
|
||||
fy="1040.8622"
|
||||
r="6.9999766"
|
||||
gradientTransform="matrix(1,0,0,0.49999621,0,520.43504)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4158"
|
||||
id="radialGradient4178-3"
|
||||
cx="7.9999766"
|
||||
cy="1040.8622"
|
||||
fx="7.9999766"
|
||||
fy="1040.8622"
|
||||
r="6.9999766"
|
||||
gradientTransform="matrix(-1,0,0,0.49999621,16,527.43502)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="18.5"
|
||||
inkscape:cx="0.59568033"
|
||||
inkscape:cy="10.721345"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:snap-bbox="true"
|
||||
inkscape:bbox-paths="true"
|
||||
inkscape:bbox-nodes="true"
|
||||
inkscape:snap-bbox-edge-midpoints="true"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:object-nodes="true"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:window-width="1600"
|
||||
inkscape:window-height="829"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4136" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1036.3622)">
|
||||
<g
|
||||
id="g4865">
|
||||
<path
|
||||
style="fill:url(#radialGradient4178);fill-opacity:1;fill-rule:evenodd;stroke:#0000a9;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1.5,1040.8622 0,1 8,0 0,2 5,-3 -5,-3 0,2 -8,0 z"
|
||||
id="path4156"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
<path
|
||||
style="fill:url(#radialGradient4178-3);fill-opacity:1;fill-rule:evenodd;stroke:#0000a9;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 14.5,1047.8622 0,1 -7.9999996,0 0,2 -5,-3 5,-3 0,2 7.9999996,0 z"
|
||||
id="path4156-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
@ -676,6 +676,23 @@ class QgsMapLayer : QObject
|
||||
*/
|
||||
void emitStyleChanged();
|
||||
|
||||
/**
|
||||
* Sets the list of layers that may modify data/geometries of this layer when modified.
|
||||
* @see dataDependencies
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
virtual bool setDataDependencies( const QSet<QString>& layersIds );
|
||||
|
||||
/**
|
||||
* Gets the list of layers that may modify data/geometries of this layer when modified.
|
||||
* @see setDataDependencies
|
||||
*
|
||||
* @returns IDs of the layers that this layer depends on
|
||||
*/
|
||||
virtual QSet<QString> dataDependencies() const;
|
||||
|
||||
signals:
|
||||
|
||||
/** 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
|
||||
void appendError( const QgsErrorMessage &error );
|
||||
/** Set error message */
|
||||
void setError( const QgsError &error );
|
||||
|
||||
//! Checks if new change dependency candidates introduce a cycle
|
||||
bool hasDataDependencyCycle( const QSet<QString>& layersIds ) const;
|
||||
};
|
||||
|
@ -422,10 +422,30 @@ class QgsVectorLayer : QgsMapLayer
|
||||
const QList<QgsVectorJoinInfo> vectorJoins() const;
|
||||
|
||||
/**
|
||||
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
|
||||
* 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.
|
||||
* When one of these layers is modified (feature added/deleted or geometry changed),
|
||||
* dataChanged() will be emitted, allowing users of this layer to refresh / update it.
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
bool setDataDependencies( const QSet<QString>& layersIds );
|
||||
|
||||
/**
|
||||
* Gets the list of layers that may modify data/geometries of this layer when modified.
|
||||
* @see setDataDependencies
|
||||
*
|
||||
* @returns IDs of the layers that this layer depends on
|
||||
*/
|
||||
QSet<QString> dataDependencies() const;
|
||||
|
||||
/**
|
||||
* Add a new field which is calculated by the expression specified
|
||||
*
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include "qgsdatasourceuri.h"
|
||||
#include "qgsrenderer.h"
|
||||
#include "qgsexpressioncontext.h"
|
||||
#include "layertree/qgslayertreelayer.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
@ -291,6 +292,23 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
|
||||
|
||||
QString title = QString( tr( "Layer Properties - %1" ) ).arg( mLayer->name() );
|
||||
restoreOptionsBaseUi( title );
|
||||
|
||||
mLayersDependenciesTreeGroup.reset( QgsProject::instance()->layerTreeRoot()->clone() );
|
||||
QgsLayerTreeLayer* layer = mLayersDependenciesTreeGroup->findLayer( mLayer->id() );
|
||||
layer->parent()->takeChild( layer );
|
||||
mLayersDependenciesTreeModel.reset( new QgsLayerTreeModel( mLayersDependenciesTreeGroup.data() ) );
|
||||
// use visibility as selection
|
||||
mLayersDependenciesTreeModel->setFlag( QgsLayerTreeModel::AllowNodeChangeVisibility );
|
||||
|
||||
mLayersDependenciesTreeGroup->setVisible( Qt::Unchecked );
|
||||
|
||||
QSet<QString> dependencySources = mLayer->dataDependencies();
|
||||
Q_FOREACH ( QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
|
||||
{
|
||||
layer->setVisible( dependencySources.contains( layer->layerId() ) ? Qt::Checked : Qt::Unchecked );
|
||||
}
|
||||
|
||||
mLayersDependenciesTreeView->setModel( mLayersDependenciesTreeModel.data() );
|
||||
} // QgsVectorLayerProperties ctor
|
||||
|
||||
|
||||
@ -558,6 +576,18 @@ void QgsVectorLayerProperties::apply()
|
||||
QgsExpressionContextUtils::setLayerVariables( mLayer, mVariableEditor->variablesInActiveScope() );
|
||||
updateVariableEditor();
|
||||
|
||||
// save layer dependencies
|
||||
QSet<QString> deps;
|
||||
Q_FOREACH ( const QgsLayerTreeLayer* layer, mLayersDependenciesTreeGroup->findLayers() )
|
||||
{
|
||||
if ( layer->isVisible() )
|
||||
deps << layer->layerId();
|
||||
}
|
||||
if ( ! mLayer->setDataDependencies( deps ) )
|
||||
{
|
||||
QMessageBox::warning( nullptr, tr( "Dependency cycle" ), tr( "This configuration introduces a cycle in data dependencies and will be ignored" ) );
|
||||
}
|
||||
|
||||
// update symbology
|
||||
emit refreshLegend( mLayer->id() );
|
||||
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include "qgscontexthelp.h"
|
||||
#include "qgsmaplayerstylemanager.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "layertree/qgslayertreemodel.h"
|
||||
#include "layertree/qgslayertreegroup.h"
|
||||
|
||||
class QgsMapLayer;
|
||||
|
||||
@ -193,6 +195,9 @@ class APP_EXPORT QgsVectorLayerProperties : public QgsOptionsDialogBase, private
|
||||
|
||||
QgsExpressionContext createExpressionContext() const override;
|
||||
|
||||
QScopedPointer<QgsLayerTreeGroup> mLayersDependenciesTreeGroup;
|
||||
QScopedPointer<QgsLayerTreeModel> mLayersDependenciesTreeModel;
|
||||
|
||||
private slots:
|
||||
void openPanel( QgsPanelWidget* panel );
|
||||
};
|
||||
|
@ -111,6 +111,22 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
|
||||
joinNode.toElement().setAttribute( "joinLayerId", newid );
|
||||
}
|
||||
}
|
||||
|
||||
// change IDs of dependencies
|
||||
QDomNodeList dataDeps = doc.elementsByTagName( "dataDependencies" );
|
||||
for ( int i = 0; i < dataDeps.size(); i++ )
|
||||
{
|
||||
QDomNodeList layers = dataDeps.at( i ).childNodes();
|
||||
for ( int j = 0; j < layers.size(); j++ )
|
||||
{
|
||||
QDomElement elt = layers.at( j ).toElement();
|
||||
if ( elt.attribute( "id" ) == oldid )
|
||||
{
|
||||
elt.setAttribute( "id", newid );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QDomElement layerTreeElem = doc.documentElement().firstChildElement( "layer-tree-group" );
|
||||
|
@ -1680,3 +1680,62 @@ void QgsMapLayer::setExtent( const QgsRectangle &r )
|
||||
{
|
||||
mExtent = r;
|
||||
}
|
||||
|
||||
static QList<const QgsMapLayer*> _depOutEdges( const QgsMapLayer* vl, const QgsMapLayer* that, const QSet<QString>& layersIds )
|
||||
{
|
||||
QList<const QgsMapLayer*> lst;
|
||||
if ( vl == that )
|
||||
{
|
||||
Q_FOREACH ( QString layerId, layersIds )
|
||||
{
|
||||
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
|
||||
lst << l;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_FOREACH ( QString layerId, vl->dataDependencies() )
|
||||
{
|
||||
if ( const QgsMapLayer* l = QgsMapLayerRegistry::instance()->mapLayer( layerId ) )
|
||||
lst << l;
|
||||
}
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
static bool _depHasCycleDFS( const QgsMapLayer* n, QHash<const QgsMapLayer*, int>& mark, const QgsMapLayer* that, const QSet<QString>& layersIds )
|
||||
{
|
||||
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 ) )
|
||||
{
|
||||
if ( _depHasCycleDFS( m, mark, that, layersIds ) )
|
||||
return true;
|
||||
}
|
||||
mark[n] = 2; // permanent
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QgsMapLayer::hasDataDependencyCycle( const QSet<QString>& layersIds ) const
|
||||
{
|
||||
QHash<const QgsMapLayer*, int> marks;
|
||||
return _depHasCycleDFS( this, marks, this, layersIds );
|
||||
}
|
||||
|
||||
bool QgsMapLayer::setDataDependencies( const QSet<QString>& layersIds )
|
||||
{
|
||||
if ( hasDataDependencyCycle( layersIds ) )
|
||||
return false;
|
||||
|
||||
mDataDependencies = layersIds;
|
||||
return true;
|
||||
}
|
||||
|
||||
QSet<QString> QgsMapLayer::dataDependencies() const
|
||||
{
|
||||
return mDataDependencies;
|
||||
}
|
||||
|
@ -698,6 +698,23 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
*/
|
||||
void emitStyleChanged();
|
||||
|
||||
/**
|
||||
* Sets the list of layers that may modify data/geometries of this layer when modified.
|
||||
* @see dataDependencies
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
virtual bool setDataDependencies( const QSet<QString>& layersIds );
|
||||
|
||||
/**
|
||||
* Gets the list of layers that may modify data/geometries of this layer when modified.
|
||||
* @see setDataDependencies
|
||||
*
|
||||
* @returns IDs of the layers that this layer depends on
|
||||
*/
|
||||
virtual QSet<QString> dataDependencies() const;
|
||||
|
||||
signals:
|
||||
|
||||
/** Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar) */
|
||||
@ -836,6 +853,12 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
/** \brief Error */
|
||||
QgsError mError;
|
||||
|
||||
//! List of layers that may modify this layer on modification
|
||||
QSet<QString> mDataDependencies;
|
||||
|
||||
//! Checks whether a new set of data dependencies will introduce a cycle
|
||||
bool hasDataDependencyCycle( const QSet<QString>& layersIds ) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* This method returns true by default but can be overwritten to specify
|
||||
|
@ -889,6 +889,12 @@ bool QgsProject::read()
|
||||
mVisibilityPresetCollection.reset( new QgsMapThemeCollection() );
|
||||
mVisibilityPresetCollection->readXml( *doc );
|
||||
|
||||
// reassign change dependencies now that all layers are loaded
|
||||
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() );
|
||||
}
|
||||
|
||||
// read the project: used by map canvas and legend
|
||||
emit readProject( *doc );
|
||||
@ -957,6 +963,8 @@ QgsExpressionContext QgsProject::createExpressionContext() const
|
||||
|
||||
void QgsProject::onMapLayersAdded( const QList<QgsMapLayer*>& layers )
|
||||
{
|
||||
QMap<QString, QgsMapLayer*> existingMaps = QgsMapLayerRegistry::instance()->mapLayers();
|
||||
|
||||
Q_FOREACH ( QgsMapLayer* layer, layers )
|
||||
{
|
||||
QgsVectorLayer* vlayer = qobject_cast<QgsVectorLayer*>( layer );
|
||||
@ -985,6 +993,17 @@ void QgsProject::onMapLayersAdded( const QList<QgsMapLayer*>& layers )
|
||||
}
|
||||
|
||||
connect( layer, SIGNAL( configChanged() ), this, SLOT( setDirty() ) );
|
||||
|
||||
// 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();
|
||||
if ( deps.contains( layer->id() ) )
|
||||
{
|
||||
// reconnect to change signals
|
||||
it.value()->setDataDependencies( deps );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1456,6 +1456,17 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
|
||||
}
|
||||
updateFields();
|
||||
|
||||
QDomNode depsNode = layer_node.namedItem( "dataDependencies" );
|
||||
QDomNodeList depsNodes = depsNode.childNodes();
|
||||
QSet<QString> sources;
|
||||
for ( int i = 0; i < depsNodes.count(); i++ )
|
||||
{
|
||||
QDomNode node = depsNodes.at( i );
|
||||
QString source = depsNodes.at( i ).toElement().attribute( "id" );
|
||||
sources << source;
|
||||
}
|
||||
setDataDependencies( sources );
|
||||
|
||||
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
|
||||
|
||||
return mValid; // should be true if read successfully
|
||||
@ -1647,6 +1658,19 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
|
||||
}
|
||||
layer_node.appendChild( defaultsElem );
|
||||
|
||||
// change dependencies
|
||||
QDomElement dataDependenciesElement = document.createElement( "dataDependencies" );
|
||||
Q_FOREACH ( QString layerId, dataDependencies() )
|
||||
{
|
||||
QDomElement depElem = document.createElement( "layer" );
|
||||
depElem.setAttribute( "id", layerId );
|
||||
dataDependenciesElement.appendChild( depElem );
|
||||
}
|
||||
layer_node.appendChild( dataDependenciesElement );
|
||||
|
||||
// save expression fields
|
||||
mExpressionFieldBuffer->writeXml( layer_node, document );
|
||||
|
||||
writeStyleManager( layer_node, document );
|
||||
|
||||
// renderer specific settings
|
||||
@ -4069,3 +4093,49 @@ QSet<QString> QgsVectorLayer::layerDependencies() const
|
||||
}
|
||||
return QSet<QString>();
|
||||
}
|
||||
|
||||
QSet<QString> QgsVectorLayer::dataDependencies() const
|
||||
{
|
||||
return layerDependencies() + mDataDependencies;
|
||||
}
|
||||
|
||||
bool QgsVectorLayer::setDataDependencies( const QSet<QString>& layersIds )
|
||||
{
|
||||
if ( hasDataDependencyCycle( layersIds ) )
|
||||
return false;
|
||||
|
||||
QSet<QString> toAdd = layerDependencies() + layersIds - mDataDependencies;
|
||||
|
||||
// disconnect layers that are not present in the list of dependencies anymore
|
||||
Q_FOREACH ( QString layerId, mDataDependencies )
|
||||
{
|
||||
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
|
||||
if ( lyr == nullptr )
|
||||
continue;
|
||||
disconnect( lyr, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
|
||||
disconnect( lyr, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
|
||||
disconnect( lyr, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SIGNAL( dataChanged() ) );
|
||||
disconnect( lyr, SIGNAL( dataChanged() ), this, SIGNAL( dataChanged() ) );
|
||||
}
|
||||
|
||||
// assign new dependencies
|
||||
mDataDependencies = layerDependencies() + layersIds;
|
||||
|
||||
// connect to new layers
|
||||
Q_FOREACH ( QString layerId, mDataDependencies )
|
||||
{
|
||||
QgsVectorLayer* lyr = static_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( layerId ) );
|
||||
if ( lyr == nullptr )
|
||||
continue;
|
||||
connect( lyr, SIGNAL( featureAdded( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
|
||||
connect( lyr, SIGNAL( featureDeleted( QgsFeatureId ) ), this, SIGNAL( dataChanged() ) );
|
||||
connect( lyr, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SIGNAL( dataChanged() ) );
|
||||
connect( lyr, SIGNAL( dataChanged() ), this, SIGNAL( dataChanged() ) );
|
||||
}
|
||||
|
||||
// if new layers are present, emit a data change
|
||||
if ( ! toAdd.isEmpty() )
|
||||
emit dataChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -515,10 +515,30 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
const QList<QgsVectorJoinInfo> vectorJoins() const;
|
||||
|
||||
/**
|
||||
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
|
||||
* 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.
|
||||
* When one of these layers is modified (feature added/deleted or geometry changed),
|
||||
* dataChanged() will be emitted, allowing users of this layer to refresh / update it.
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
bool setDataDependencies( const QSet<QString>& layersIds ) override;
|
||||
|
||||
/**
|
||||
* Gets the list of layers that may modify data/geometries of this layer when modified.
|
||||
* @see setDataDependencies
|
||||
*
|
||||
* @returns IDs of the layers that this layer depends on
|
||||
*/
|
||||
QSet<QString> dataDependencies() const override;
|
||||
|
||||
/**
|
||||
* Add a new field which is calculated by the expression specified
|
||||
*
|
||||
|
@ -46,16 +46,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -231,6 +222,15 @@
|
||||
<normaloff>:/images/themes/default/legend.svg</normaloff>:/images/themes/default/legend.svg</iconset>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Data dependencies</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/dependencies.svg</normaloff>:/images/themes/default/dependencies.svg</iconset>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -249,16 +249,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -274,16 +265,7 @@
|
||||
</property>
|
||||
<widget class="QWidget" name="mOptsPage_General">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_14">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -304,16 +286,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_13">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -335,16 +308,7 @@
|
||||
<item row="5" column="0">
|
||||
<widget class="QFrame" name="mDataSourceEncodingFrame">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</layout>
|
||||
@ -572,16 +536,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Style">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -597,21 +552,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
<width>730</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_18">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -641,16 +587,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Labels">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_12">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -673,16 +610,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Fields">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_15">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -698,21 +626,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
<width>730</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_20">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -733,16 +652,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Rendering">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_19">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -908,16 +818,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Display">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_25">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1003,16 +904,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Actions">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_16">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1028,21 +920,12 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
<width>730</width>
|
||||
<height>537</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_21">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1069,16 +952,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Joins">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1099,16 +973,7 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_23">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1206,16 +1071,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Diagrams">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_10">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1238,16 +1094,7 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_Metadata">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
@ -1702,6 +1549,46 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mOptsPage_DataDependencies">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_29">
|
||||
<item>
|
||||
<widget class="QgsCollapsibleGroupBox" name="groupBox_5">
|
||||
<property name="title">
|
||||
<string>Data dependencies</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_24">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Data of this layer may be updated by data change on one of these layers :</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QgsLayerTreeView" name="mLayersDependenciesTreeView">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -1723,16 +1610,7 @@
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_btnbox">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="2" column="1" colspan="4">
|
||||
@ -1795,6 +1673,9 @@
|
||||
<extends>QFrame</extends>
|
||||
<header>qgscodeeditorhtml.h</header>
|
||||
<container>1</container>
|
||||
<class>QgsLayerTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header>qgslayertreeview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
|
@ -108,6 +108,7 @@ ADD_PYTHON_TEST(PyQgsLayerDefinition test_qgslayerdefinition.py)
|
||||
ADD_PYTHON_TEST(PyQgsWFSProvider test_provider_wfs.py)
|
||||
ADD_PYTHON_TEST(PyQgsWFSProviderGUI test_provider_wfs_gui.py)
|
||||
ADD_PYTHON_TEST(PyQgsConsole test_console.py)
|
||||
ADD_PYTHON_TEST(PyQgsLayerDependencies test_layer_dependencies.py)
|
||||
|
||||
IF (NOT WIN32)
|
||||
ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)
|
||||
|
259
tests/src/python/test_layer_dependencies.py
Normal file
259
tests/src/python/test_layer_dependencies.py
Normal file
@ -0,0 +1,259 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit tests for QgsSnappingUtils (complement to C++-based tests)
|
||||
|
||||
.. note:: 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.
|
||||
"""
|
||||
__author__ = 'Hugo Mercier'
|
||||
__date__ = '12/07/2016'
|
||||
__copyright__ = 'Copyright 2016, The QGIS Project'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
import qgis # NOQA
|
||||
import os
|
||||
|
||||
from qgis.core import (QgsMapLayerRegistry,
|
||||
QgsVectorLayer,
|
||||
QgsMapSettings,
|
||||
QgsSnappingUtils,
|
||||
QgsPointLocator,
|
||||
QgsTolerance,
|
||||
QgsRectangle,
|
||||
QgsPoint,
|
||||
QgsFeature,
|
||||
QgsGeometry,
|
||||
QgsProject,
|
||||
QgsLayerDefinition
|
||||
)
|
||||
|
||||
from qgis.testing import start_app, unittest
|
||||
from utilities import unitTestDataPath
|
||||
|
||||
from qgis.PyQt.QtCore import QSize, QPoint
|
||||
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from pyspatialite import dbapi2 as sqlite3
|
||||
except ImportError:
|
||||
print("You should install pyspatialite to run the tests")
|
||||
raise ImportError
|
||||
|
||||
# Convenience instances in case you may need them
|
||||
start_app()
|
||||
|
||||
|
||||
class TestLayerDependencies(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run before all tests"""
|
||||
|
||||
# create a temp spatialite db with a trigger
|
||||
fo = tempfile.NamedTemporaryFile()
|
||||
fn = fo.name
|
||||
fo.close()
|
||||
cls.fn = fn
|
||||
con = sqlite3.connect(fn)
|
||||
cur = con.cursor()
|
||||
cur.execute("SELECT InitSpatialMetadata(1)")
|
||||
cur.execute("create table node(id integer primary key autoincrement);")
|
||||
cur.execute("select AddGeometryColumn('node', 'geom', 4326, 'POINT');")
|
||||
cur.execute("create table section(id integer primary key autoincrement, node1 integer, node2 integer);")
|
||||
cur.execute("select AddGeometryColumn('section', 'geom', 4326, 'LINESTRING');")
|
||||
cur.execute("create trigger add_nodes after insert on section begin insert into node (geom) values (st_startpoint(NEW.geom)); insert into node (geom) values (st_endpoint(NEW.geom)); end;")
|
||||
cur.execute("insert into node (geom) values (geomfromtext('point(0 0)', 4326));")
|
||||
cur.execute("insert into node (geom) values (geomfromtext('point(1 0)', 4326));")
|
||||
cur.execute("create table node2(id integer primary key autoincrement);")
|
||||
cur.execute("select AddGeometryColumn('node2', 'geom', 4326, 'POINT');")
|
||||
cur.execute("create trigger add_nodes2 after insert on node begin insert into node2 (geom) values (st_translate(NEW.geom, 0.2, 0, 0)); end;")
|
||||
con.commit()
|
||||
con.close()
|
||||
|
||||
cls.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % fn, "points", "spatialite")
|
||||
assert (cls.pointsLayer.isValid())
|
||||
cls.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % fn, "lines", "spatialite")
|
||||
assert (cls.linesLayer.isValid())
|
||||
cls.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % fn, "_points2", "spatialite")
|
||||
assert (cls.pointsLayer2.isValid())
|
||||
QgsMapLayerRegistry.instance().addMapLayers([cls.pointsLayer, cls.linesLayer, cls.pointsLayer2])
|
||||
|
||||
# save the project file
|
||||
fo = tempfile.NamedTemporaryFile()
|
||||
fn = fo.name
|
||||
fo.close()
|
||||
cls.projectFile = fn
|
||||
QgsProject.instance().setFileName(cls.projectFile)
|
||||
QgsProject.instance().write()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
"""Run after all tests"""
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
"""Run before each test."""
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
"""Run after each test."""
|
||||
pass
|
||||
|
||||
def test_resetSnappingIndex(self):
|
||||
self.pointsLayer.setDataDependencies([])
|
||||
self.linesLayer.setDataDependencies([])
|
||||
self.pointsLayer2.setDataDependencies([])
|
||||
|
||||
ms = QgsMapSettings()
|
||||
ms.setOutputSize(QSize(100, 100))
|
||||
ms.setExtent(QgsRectangle(0, 0, 1, 1))
|
||||
self.assertTrue(ms.hasValidSettings())
|
||||
|
||||
u = QgsSnappingUtils()
|
||||
u.setMapSettings(ms)
|
||||
u.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced)
|
||||
layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)]
|
||||
u.setLayers(layers)
|
||||
|
||||
m = u.snapToMap(QPoint(95, 100))
|
||||
self.assertTrue(m.isValid())
|
||||
self.assertTrue(m.hasVertex())
|
||||
self.assertEqual(m.point(), QgsPoint(1, 0))
|
||||
|
||||
f = QgsFeature(self.linesLayer.fields())
|
||||
f.setFeatureId(1)
|
||||
geom = QgsGeometry.fromWkt("LINESTRING(0 0,1 1)")
|
||||
f.setGeometry(geom)
|
||||
self.linesLayer.startEditing()
|
||||
self.linesLayer.addFeatures([f])
|
||||
self.linesLayer.commitChanges()
|
||||
|
||||
l1 = len([f for f in self.pointsLayer.getFeatures()])
|
||||
self.assertEqual(l1, 4)
|
||||
m = u.snapToMap(QPoint(95, 0))
|
||||
# snapping not updated
|
||||
self.assertEqual(m.isValid(), False)
|
||||
|
||||
# set layer dependencies
|
||||
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
|
||||
# add another line
|
||||
f = QgsFeature(self.linesLayer.fields())
|
||||
f.setFeatureId(2)
|
||||
geom = QgsGeometry.fromWkt("LINESTRING(0 0,0.5 0.5)")
|
||||
f.setGeometry(geom)
|
||||
self.linesLayer.startEditing()
|
||||
self.linesLayer.addFeatures([f])
|
||||
self.linesLayer.commitChanges()
|
||||
# check the snapped point is ok
|
||||
m = u.snapToMap(QPoint(45, 50))
|
||||
self.assertTrue(m.isValid())
|
||||
self.assertTrue(m.hasVertex())
|
||||
self.assertEqual(m.point(), QgsPoint(0.5, 0.5))
|
||||
self.pointsLayer.setDataDependencies([])
|
||||
|
||||
# test chained layer dependencies A -> B -> C
|
||||
layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels),
|
||||
QgsSnappingUtils.LayerConfig(self.pointsLayer2, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)
|
||||
]
|
||||
u.setLayers(layers)
|
||||
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
|
||||
self.pointsLayer2.setDataDependencies([self.pointsLayer.id()])
|
||||
# add another line
|
||||
f = QgsFeature(self.linesLayer.fields())
|
||||
f.setFeatureId(3)
|
||||
geom = QgsGeometry.fromWkt("LINESTRING(0 0.2,0.5 0.8)")
|
||||
f.setGeometry(geom)
|
||||
self.linesLayer.startEditing()
|
||||
self.linesLayer.addFeatures([f])
|
||||
self.linesLayer.commitChanges()
|
||||
# check the second snapped point is ok
|
||||
m = u.snapToMap(QPoint(75, 100 - 80))
|
||||
self.assertTrue(m.isValid())
|
||||
self.assertTrue(m.hasVertex())
|
||||
self.assertEqual(m.point(), QgsPoint(0.7, 0.8))
|
||||
self.pointsLayer.setDataDependencies([])
|
||||
self.pointsLayer2.setDataDependencies([])
|
||||
|
||||
def test_cycleDetection(self):
|
||||
self.assertTrue(self.pointsLayer.setDataDependencies([self.linesLayer.id()]))
|
||||
self.assertFalse(self.linesLayer.setDataDependencies([self.pointsLayer.id()]))
|
||||
self.pointsLayer.setDataDependencies([])
|
||||
self.linesLayer.setDataDependencies([])
|
||||
|
||||
def test_layerDefinitionRewriteId(self):
|
||||
tmpfile = os.path.join(tempfile.tempdir, "test.qlr")
|
||||
|
||||
ltr = QgsProject.instance().layerTreeRoot()
|
||||
|
||||
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
|
||||
|
||||
QgsLayerDefinition.exportLayerDefinition(tmpfile, [ltr])
|
||||
|
||||
grp = ltr.addGroup("imported")
|
||||
QgsLayerDefinition.loadLayerDefinition(tmpfile, grp)
|
||||
|
||||
newPointsLayer = None
|
||||
newLinesLayer = None
|
||||
for l in grp.findLayers():
|
||||
if l.layerId().startswith('points'):
|
||||
newPointsLayer = l.layer()
|
||||
elif l.layerId().startswith('lines'):
|
||||
newLinesLayer = l.layer()
|
||||
self.assertFalse(newPointsLayer is None)
|
||||
self.assertFalse(newLinesLayer is None)
|
||||
self.assertTrue(newLinesLayer.id() in newPointsLayer.dataDependencies())
|
||||
|
||||
self.pointsLayer.setDataDependencies([])
|
||||
|
||||
def test_signalConnection(self):
|
||||
# remove all layers
|
||||
QgsMapLayerRegistry.instance().removeAllMapLayers()
|
||||
# set dependencies and add back layers
|
||||
self.pointsLayer = QgsVectorLayer("dbname='%s' table=\"node\" (geom) sql=" % self.fn, "points", "spatialite")
|
||||
assert (self.pointsLayer.isValid())
|
||||
self.linesLayer = QgsVectorLayer("dbname='%s' table=\"section\" (geom) sql=" % self.fn, "lines", "spatialite")
|
||||
assert (self.linesLayer.isValid())
|
||||
self.pointsLayer2 = QgsVectorLayer("dbname='%s' table=\"node2\" (geom) sql=" % self.fn, "_points2", "spatialite")
|
||||
assert (self.pointsLayer2.isValid())
|
||||
self.pointsLayer.setDataDependencies([self.linesLayer.id()])
|
||||
self.pointsLayer2.setDataDependencies([self.pointsLayer.id()])
|
||||
# this should update connections between layers
|
||||
QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer])
|
||||
QgsMapLayerRegistry.instance().addMapLayers([self.linesLayer])
|
||||
QgsMapLayerRegistry.instance().addMapLayers([self.pointsLayer2])
|
||||
|
||||
ms = QgsMapSettings()
|
||||
ms.setOutputSize(QSize(100, 100))
|
||||
ms.setExtent(QgsRectangle(0, 0, 1, 1))
|
||||
self.assertTrue(ms.hasValidSettings())
|
||||
|
||||
u = QgsSnappingUtils()
|
||||
u.setMapSettings(ms)
|
||||
u.setSnapToMapMode(QgsSnappingUtils.SnapAdvanced)
|
||||
layers = [QgsSnappingUtils.LayerConfig(self.pointsLayer, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels),
|
||||
QgsSnappingUtils.LayerConfig(self.pointsLayer2, QgsPointLocator.Vertex, 20, QgsTolerance.Pixels)
|
||||
]
|
||||
u.setLayers(layers)
|
||||
# add another line
|
||||
f = QgsFeature(self.linesLayer.fields())
|
||||
f.setFeatureId(4)
|
||||
geom = QgsGeometry.fromWkt("LINESTRING(0.5 0.2,0.6 0)")
|
||||
f.setGeometry(geom)
|
||||
self.linesLayer.startEditing()
|
||||
self.linesLayer.addFeatures([f])
|
||||
self.linesLayer.commitChanges()
|
||||
# check the second snapped point is ok
|
||||
m = u.snapToMap(QPoint(75, 100 - 0))
|
||||
self.assertTrue(m.isValid())
|
||||
self.assertTrue(m.hasVertex())
|
||||
self.assertEqual(m.point(), QgsPoint(0.8, 0.0))
|
||||
|
||||
self.pointsLayer.setDataDependencies([])
|
||||
self.pointsLayer2.setDataDependencies([])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user