Skip to content

Commit f351715

Browse files
author
Hugo Mercier
committedJan 7, 2016
Merge pull request #2635 from mhugo/layer_dependencies
Layer dependencies
2 parents 2b7c5c1 + 7e5915e commit f351715

19 files changed

+430
-12
lines changed
 

‎python/core/qgslayerdefinition.sip

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,34 @@ class QgsLayerDefinition
1919
static bool exportLayerDefinition( QString path, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage /Out/ );
2020
/** Export the selected layer tree nodes to a QLR-XML document */
2121
static bool exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage, const QString& relativeBasePath = QString::null );
22+
23+
/**
24+
* Class used to work with layer dependencies stored in a XML project or layer definition file
25+
*/
26+
class DependencySorter
27+
{
28+
public:
29+
/** Constructor
30+
* @param doc The XML document containing maplayer elements
31+
*/
32+
DependencySorter( QDomDocument doc );
33+
34+
/** Constructor
35+
* @param fileName The filename where the XML document is stored
36+
*/
37+
DependencySorter( const QString& fileName );
38+
39+
/** Get the layer nodes in an order where they can be loaded incrementally without dependency break */
40+
QVector<QDomNode> sortedLayerNodes() const;
41+
42+
/** Get the layer IDs in an order where they can be loaded incrementally without dependency break */
43+
QStringList sortedLayerIds() const;
44+
45+
/** Whether some cyclic dependency has been detected */
46+
bool hasCycle() const;
47+
48+
/** Whether some dependency is missing */
49+
bool hasMissingDependency() const;
50+
};
2251
};
2352

‎python/core/qgsmaplayer.sip

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ class QgsMapLayer : QObject
317317

318318
/** Creates a new layer from a layer defininition document
319319
*/
320-
static QList<QgsMapLayer*> fromLayerDefinition( QDomDocument& document );
320+
static QList<QgsMapLayer*> fromLayerDefinition( QDomDocument& document, bool addToRegistry = false, bool addToLegend = false );
321321
static QList<QgsMapLayer*> fromLayerDefinitionFile( const QString &qlrfile );
322322

323323
/** Set a custom property for layer. Properties are stored in a map and saved in project file. */

‎python/core/qgsvectordataprovider.sip

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,11 @@ class QgsVectorDataProvider : QgsDataProvider
322322

323323
virtual void forceReload();
324324

325+
/**
326+
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
327+
*/
328+
virtual QSet<QString> layerDependencies() const;
329+
325330
protected:
326331
void clearMinMaxCache();
327332
void fillMinMaxCache();

‎python/core/qgsvectorlayer.sip

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,11 @@ class QgsVectorLayer : QgsMapLayer
441441

442442
const QList<QgsVectorJoinInfo> vectorJoins() const;
443443

444+
/**
445+
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
446+
*/
447+
virtual QSet<QString> layerDependencies() const;
448+
444449
/**
445450
* Add a new field which is calculated by the expression specified
446451
*

‎src/app/qgisapp.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8467,11 +8467,18 @@ void QgisApp::embedLayers()
84678467
//layer ids
84688468
QList<QDomNode> brokenNodes;
84698469
QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;
8470+
8471+
// resolve dependencies
8472+
QgsLayerDefinition::DependencySorter depSorter( projectFile );
8473+
QStringList sortedIds = depSorter.sortedLayerIds();
84708474
QStringList layerIds = d.selectedLayerIds();
8471-
QStringList::const_iterator layerIt = layerIds.constBegin();
8472-
for ( ; layerIt != layerIds.constEnd(); ++layerIt )
8475+
foreach ( QString id, sortedIds )
84738476
{
8474-
QgsProject::instance()->createEmbeddedLayer( *layerIt, projectFile, brokenNodes, vectorLayerList );
8477+
foreach ( QString selId, layerIds )
8478+
{
8479+
if ( selId == id )
8480+
QgsProject::instance()->createEmbeddedLayer( selId, projectFile, brokenNodes, vectorLayerList );
8481+
}
84758482
}
84768483

84778484
mMapCanvas->freeze( false );

‎src/core/qgslayerdefinition.cpp

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,33 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
4040

4141
QgsLayerTreeGroup *root = new QgsLayerTreeGroup();
4242

43+
// reorder maplayer nodes based on dependencies
44+
// dependencies have to be resolved before IDs get changed
45+
DependencySorter depSorter( doc );
46+
if ( !depSorter.hasMissingDependency() )
47+
{
48+
QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
49+
QVector<QDomNode> clonedSorted;
50+
foreach ( QDomNode node, sortedLayerNodes )
51+
{
52+
clonedSorted << node.cloneNode();
53+
}
54+
QDomNode layersNode = doc.elementsByTagName( "maplayers" ).at( 0 );
55+
// remove old children
56+
QDomNodeList childNodes = layersNode.childNodes();
57+
for ( int i = 0; i < childNodes.size(); i++ )
58+
{
59+
layersNode.removeChild( childNodes.at( i ) );
60+
}
61+
// replace with new ones
62+
foreach ( QDomNode node, clonedSorted )
63+
{
64+
layersNode.appendChild( node );
65+
}
66+
}
67+
// if a dependency is missing, we still try to load layers, since dependencies may already be loaded
68+
69+
// IDs of layers should be changed otherwise we may have more then one layer with the same id
4370
// We have to replace the IDs before we load them because it's too late once they are loaded
4471
QDomNodeList ids = doc.elementsByTagName( "id" );
4572
for ( int i = 0; i < ids.size(); ++i )
@@ -85,8 +112,7 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
85112
loadInLegend = false;
86113
}
87114

88-
QList<QgsMapLayer*> layers = QgsMapLayer::fromLayerDefinition( doc );
89-
QgsMapLayerRegistry::instance()->addMapLayers( layers, loadInLegend );
115+
QList<QgsMapLayer*> layers = QgsMapLayer::fromLayerDefinition( doc, /*addToRegistry*/ true, loadInLegend );
90116

91117
// Now that all layers are loaded, refresh the vectorjoins to get the joined fields
92118
Q_FOREACH ( QgsMapLayer* layer, layers )
@@ -159,3 +185,115 @@ bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<Qg
159185
qgiselm.appendChild( layerselm );
160186
return true;
161187
}
188+
189+
void QgsLayerDefinition::DependencySorter::init( QDomDocument doc )
190+
{
191+
// Determine a loading order of layers based on a graph of dependencies
192+
QMap< QString, QVector< QString > > dependencies;
193+
QStringList sortedLayers;
194+
QList< QPair<QString, QDomNode> > layersToSort;
195+
QStringList layerIds;
196+
197+
QDomNodeList nl = doc.elementsByTagName( "maplayer" );
198+
for ( int i = 0; i < nl.count(); i++ )
199+
{
200+
QVector<QString> deps;
201+
QDomNode node = nl.item( i );
202+
QDomElement element = node.toElement();
203+
204+
QString id = node.namedItem( "id" ).toElement().text();
205+
layerIds << id;
206+
207+
// dependencies for this layer
208+
QDomElement layerDependenciesElem = node.firstChildElement( "layerDependencies" );
209+
if ( !layerDependenciesElem.isNull() )
210+
{
211+
QDomNodeList dependencyList = layerDependenciesElem.elementsByTagName( "layer" );
212+
for ( int j = 0; j < dependencyList.size(); ++j )
213+
{
214+
QDomElement depElem = dependencyList.at( j ).toElement();
215+
deps << depElem.attribute( "id" );
216+
}
217+
}
218+
dependencies[id] = deps;
219+
220+
if ( deps.empty() )
221+
{
222+
sortedLayers << id;
223+
mSortedLayerNodes << node;
224+
}
225+
else
226+
layersToSort << qMakePair( id, node );
227+
}
228+
229+
// check that all dependencies are present
230+
foreach ( QString id, dependencies.keys() )
231+
{
232+
foreach ( QString depId, dependencies[id] )
233+
{
234+
if ( !dependencies.contains( depId ) )
235+
{
236+
// some dependencies are not satisfied
237+
mHasMissingDependency = true;
238+
for ( int i = 0; i < nl.size(); i++ )
239+
mSortedLayerNodes << nl.at( i );
240+
mSortedLayerIds = layerIds;
241+
return;
242+
}
243+
}
244+
}
245+
246+
// cycles should be very rare, since layers with cyclic dependencies may only be created by
247+
// manually modifying the project file
248+
mHasCycle = false;
249+
250+
while ( !layersToSort.empty() && !mHasCycle )
251+
{
252+
QList< QPair<QString, QDomNode> >::iterator it = layersToSort.begin();
253+
while ( it != layersToSort.end() )
254+
{
255+
QString idToSort = it->first;
256+
QDomNode node = it->second;
257+
mHasCycle = true;
258+
bool resolved = true;
259+
foreach ( QString dep, dependencies[idToSort] )
260+
{
261+
if ( !sortedLayers.contains( dep ) )
262+
{
263+
resolved = false;
264+
break;
265+
}
266+
}
267+
if ( resolved ) // dependencies for this layer are resolved
268+
{
269+
sortedLayers << idToSort;
270+
mSortedLayerNodes << node;
271+
mSortedLayerIds << idToSort;
272+
it = layersToSort.erase( it ); // erase and go to the next
273+
mHasCycle = false;
274+
}
275+
else
276+
{
277+
it++;
278+
}
279+
}
280+
}
281+
}
282+
283+
QgsLayerDefinition::DependencySorter::DependencySorter( QDomDocument doc ) :
284+
mHasCycle( false ), mHasMissingDependency( false )
285+
{
286+
init( doc );
287+
}
288+
289+
QgsLayerDefinition::DependencySorter::DependencySorter( const QString& fileName ) :
290+
mHasCycle( false ), mHasMissingDependency( false )
291+
{
292+
QDomDocument doc;
293+
QFile pFile( fileName );
294+
pFile.open( QIODevice::ReadOnly );
295+
doc.setContent( &pFile );
296+
init( doc );
297+
}
298+
299+

‎src/core/qgslayerdefinition.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,42 @@ class CORE_EXPORT QgsLayerDefinition
2121
static bool exportLayerDefinition( QString path, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage );
2222
/** Export the selected layer tree nodes to a QLR-XML document */
2323
static bool exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage, const QString& relativeBasePath = QString::null );
24+
25+
/**
26+
* Class used to work with layer dependencies stored in a XML project or layer definition file
27+
*/
28+
class CORE_EXPORT DependencySorter
29+
{
30+
public:
31+
/** Constructor
32+
* @param doc The XML document containing maplayer elements
33+
*/
34+
DependencySorter( QDomDocument doc );
35+
36+
/** Constructor
37+
* @param fileName The filename where the XML document is stored
38+
*/
39+
DependencySorter( const QString& fileName );
40+
41+
/** Get the layer nodes in an order where they can be loaded incrementally without dependency break */
42+
QVector<QDomNode> sortedLayerNodes() const { return mSortedLayerNodes; }
43+
44+
/** Get the layer IDs in an order where they can be loaded incrementally without dependency break */
45+
QStringList sortedLayerIds() const { return mSortedLayerIds; }
46+
47+
/** Whether some cyclic dependency has been detected */
48+
bool hasCycle() const { return mHasCycle; }
49+
50+
/** Whether some dependency is missing */
51+
bool hasMissingDependency() const { return mHasMissingDependency; }
52+
53+
private:
54+
QVector<QDomNode> mSortedLayerNodes;
55+
QStringList mSortedLayerIds;
56+
bool mHasCycle;
57+
bool mHasMissingDependency;
58+
void init( QDomDocument doc );
59+
};
2460
};
2561

2662
#endif // QGSLAYERDEFINITION_H

‎src/core/qgsmaplayer.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "qgsrectangle.h"
4848
#include "qgsvectorlayer.h"
4949
#include "qgsvectordataprovider.h"
50+
#include "qgsmaplayerregistry.h"
5051

5152

5253
QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type,
@@ -788,7 +789,7 @@ QDomDocument QgsMapLayer::asLayerDefinition( const QList<QgsMapLayer *>& layers,
788789
return doc;
789790
}
790791

791-
QList<QgsMapLayer*> QgsMapLayer::fromLayerDefinition( QDomDocument& document )
792+
QList<QgsMapLayer*> QgsMapLayer::fromLayerDefinition( QDomDocument& document, bool addToRegistry, bool addToLegend )
792793
{
793794
QList<QgsMapLayer*> layers;
794795
QDomNodeList layernodes = document.elementsByTagName( "maplayer" );
@@ -820,7 +821,11 @@ QList<QgsMapLayer*> QgsMapLayer::fromLayerDefinition( QDomDocument& document )
820821

821822
bool ok = layer->readLayerXML( layerElem );
822823
if ( ok )
824+
{
823825
layers << layer;
826+
if ( addToRegistry )
827+
QgsMapLayerRegistry::instance()->addMapLayer( layer, addToLegend );
828+
}
824829
}
825830
return layers;
826831
}

‎src/core/qgsmaplayer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ class CORE_EXPORT QgsMapLayer : public QObject
333333

334334
/** Creates a new layer from a layer defininition document
335335
*/
336-
static QList<QgsMapLayer*> fromLayerDefinition( QDomDocument& document );
336+
static QList<QgsMapLayer*> fromLayerDefinition( QDomDocument& document, bool addToRegistry = false, bool addToLegend = false );
337337
static QList<QgsMapLayer*> fromLayerDefinitionFile( const QString &qlrfile );
338338

339339
/** Set a custom property for layer. Properties are stored in a map and saved in project file. */

‎src/core/qgsproject.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "qgsrelationmanager.h"
3636
#include "qgsvectorlayer.h"
3737
#include "qgsvisibilitypresetcollection.h"
38+
#include "qgslayerdefinition.h"
3839

3940
#include <QApplication>
4041
#include <QFileInfo>
@@ -672,13 +673,19 @@ QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &do
672673

673674
emit layerLoaded( 0, nl.count() );
674675

676+
// order layers based on their dependencies
677+
QgsLayerDefinition::DependencySorter depSorter( doc );
678+
if ( depSorter.hasCycle() || depSorter.hasMissingDependency() )
679+
return qMakePair( false, QList<QDomNode>() );
680+
681+
QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
682+
675683
// Collect vector layers with joins.
676684
// They need to refresh join caches and symbology infos after all layers are loaded
677685
QList< QPair< QgsVectorLayer*, QDomElement > > vLayerList;
678-
679-
for ( int i = 0; i < nl.count(); i++ )
686+
int i = 0;
687+
foreach ( QDomNode node, sortedLayerNodes )
680688
{
681-
QDomNode node = nl.item( i );
682689
QDomElement element = node.toElement();
683690

684691
QString name = node.namedItem( "layername" ).toElement().text();
@@ -698,6 +705,7 @@ QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &do
698705
}
699706
}
700707
emit layerLoaded( i + 1, nl.count() );
708+
i++;
701709
}
702710

703711
// Update field map of layers with joins and create join caches if necessary

‎src/core/qgsvectordataprovider.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,4 +548,9 @@ void QgsVectorDataProvider::pushError( const QString& msg )
548548
mErrors << msg;
549549
}
550550

551+
QSet<QString> QgsVectorDataProvider::layerDependencies() const
552+
{
553+
return QSet<QString>();
554+
}
555+
551556
QStringList QgsVectorDataProvider::smEncodings;

‎src/core/qgsvectordataprovider.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,11 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
383383
emit dataChanged();
384384
}
385385

386+
/**
387+
* Get the list of layer ids on which this layer depends. This in particular determines the order of layer loading.
388+
*/
389+
virtual QSet<QString> layerDependencies() const;
390+
386391
protected:
387392
void clearMinMaxCache();
388393
void fillMinMaxCache();

0 commit comments

Comments
 (0)
Please sign in to comment.