Skip to content

Commit

Permalink
Merge pull request #2635 from mhugo/layer_dependencies
Browse files Browse the repository at this point in the history
Layer dependencies
  • Loading branch information
Hugo Mercier committed Jan 7, 2016
2 parents 2b7c5c1 + 7e5915e commit f351715
Show file tree
Hide file tree
Showing 19 changed files with 430 additions and 12 deletions.
29 changes: 29 additions & 0 deletions python/core/qgslayerdefinition.sip
Expand Up @@ -19,5 +19,34 @@ class QgsLayerDefinition
static bool exportLayerDefinition( QString path, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage /Out/ );
/** Export the selected layer tree nodes to a QLR-XML document */
static bool exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage, const QString& relativeBasePath = QString::null );

/**
* Class used to work with layer dependencies stored in a XML project or layer definition file
*/
class DependencySorter
{
public:
/** Constructor
* @param doc The XML document containing maplayer elements
*/
DependencySorter( QDomDocument doc );

/** Constructor
* @param fileName The filename where the XML document is stored
*/
DependencySorter( const QString& fileName );

/** Get the layer nodes in an order where they can be loaded incrementally without dependency break */
QVector<QDomNode> sortedLayerNodes() const;

/** Get the layer IDs in an order where they can be loaded incrementally without dependency break */
QStringList sortedLayerIds() const;

/** Whether some cyclic dependency has been detected */
bool hasCycle() const;

/** Whether some dependency is missing */
bool hasMissingDependency() const;
};
};

2 changes: 1 addition & 1 deletion python/core/qgsmaplayer.sip
Expand Up @@ -317,7 +317,7 @@ class QgsMapLayer : QObject

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

/** Set a custom property for layer. Properties are stored in a map and saved in project file. */
Expand Down
5 changes: 5 additions & 0 deletions python/core/qgsvectordataprovider.sip
Expand Up @@ -322,6 +322,11 @@ class QgsVectorDataProvider : QgsDataProvider

virtual void forceReload();

/**
* 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;

protected:
void clearMinMaxCache();
void fillMinMaxCache();
Expand Down
5 changes: 5 additions & 0 deletions python/core/qgsvectorlayer.sip
Expand Up @@ -441,6 +441,11 @@ 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.
*/
virtual QSet<QString> layerDependencies() const;

/**
* Add a new field which is calculated by the expression specified
*
Expand Down
13 changes: 10 additions & 3 deletions src/app/qgisapp.cpp
Expand Up @@ -8467,11 +8467,18 @@ void QgisApp::embedLayers()
//layer ids
QList<QDomNode> brokenNodes;
QList< QPair< QgsVectorLayer*, QDomElement > > vectorLayerList;

// resolve dependencies
QgsLayerDefinition::DependencySorter depSorter( projectFile );
QStringList sortedIds = depSorter.sortedLayerIds();
QStringList layerIds = d.selectedLayerIds();
QStringList::const_iterator layerIt = layerIds.constBegin();
for ( ; layerIt != layerIds.constEnd(); ++layerIt )
foreach ( QString id, sortedIds )
{
QgsProject::instance()->createEmbeddedLayer( *layerIt, projectFile, brokenNodes, vectorLayerList );
foreach ( QString selId, layerIds )
{
if ( selId == id )
QgsProject::instance()->createEmbeddedLayer( selId, projectFile, brokenNodes, vectorLayerList );
}
}

mMapCanvas->freeze( false );
Expand Down
142 changes: 140 additions & 2 deletions src/core/qgslayerdefinition.cpp
Expand Up @@ -40,6 +40,33 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou

QgsLayerTreeGroup *root = new QgsLayerTreeGroup();

// reorder maplayer nodes based on dependencies
// dependencies have to be resolved before IDs get changed
DependencySorter depSorter( doc );
if ( !depSorter.hasMissingDependency() )
{
QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
QVector<QDomNode> clonedSorted;
foreach ( QDomNode node, sortedLayerNodes )
{
clonedSorted << node.cloneNode();
}
QDomNode layersNode = doc.elementsByTagName( "maplayers" ).at( 0 );
// remove old children
QDomNodeList childNodes = layersNode.childNodes();
for ( int i = 0; i < childNodes.size(); i++ )
{
layersNode.removeChild( childNodes.at( i ) );
}
// replace with new ones
foreach ( QDomNode node, clonedSorted )
{
layersNode.appendChild( node );
}
}
// if a dependency is missing, we still try to load layers, since dependencies may already be loaded

// IDs of layers should be changed otherwise we may have more then one layer with the same id
// We have to replace the IDs before we load them because it's too late once they are loaded
QDomNodeList ids = doc.elementsByTagName( "id" );
for ( int i = 0; i < ids.size(); ++i )
Expand Down Expand Up @@ -85,8 +112,7 @@ bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsLayerTreeGrou
loadInLegend = false;
}

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

// Now that all layers are loaded, refresh the vectorjoins to get the joined fields
Q_FOREACH ( QgsMapLayer* layer, layers )
Expand Down Expand Up @@ -159,3 +185,115 @@ bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<Qg
qgiselm.appendChild( layerselm );
return true;
}

void QgsLayerDefinition::DependencySorter::init( QDomDocument doc )
{
// Determine a loading order of layers based on a graph of dependencies
QMap< QString, QVector< QString > > dependencies;
QStringList sortedLayers;
QList< QPair<QString, QDomNode> > layersToSort;
QStringList layerIds;

QDomNodeList nl = doc.elementsByTagName( "maplayer" );
for ( int i = 0; i < nl.count(); i++ )
{
QVector<QString> deps;
QDomNode node = nl.item( i );
QDomElement element = node.toElement();

QString id = node.namedItem( "id" ).toElement().text();
layerIds << id;

// dependencies for this layer
QDomElement layerDependenciesElem = node.firstChildElement( "layerDependencies" );
if ( !layerDependenciesElem.isNull() )
{
QDomNodeList dependencyList = layerDependenciesElem.elementsByTagName( "layer" );
for ( int j = 0; j < dependencyList.size(); ++j )
{
QDomElement depElem = dependencyList.at( j ).toElement();
deps << depElem.attribute( "id" );
}
}
dependencies[id] = deps;

if ( deps.empty() )
{
sortedLayers << id;
mSortedLayerNodes << node;
}
else
layersToSort << qMakePair( id, node );
}

// check that all dependencies are present
foreach ( QString id, dependencies.keys() )
{
foreach ( QString depId, dependencies[id] )
{
if ( !dependencies.contains( depId ) )
{
// some dependencies are not satisfied
mHasMissingDependency = true;
for ( int i = 0; i < nl.size(); i++ )
mSortedLayerNodes << nl.at( i );
mSortedLayerIds = layerIds;
return;
}
}
}

// cycles should be very rare, since layers with cyclic dependencies may only be created by
// manually modifying the project file
mHasCycle = false;

while ( !layersToSort.empty() && !mHasCycle )
{
QList< QPair<QString, QDomNode> >::iterator it = layersToSort.begin();
while ( it != layersToSort.end() )
{
QString idToSort = it->first;
QDomNode node = it->second;
mHasCycle = true;
bool resolved = true;
foreach ( QString dep, dependencies[idToSort] )
{
if ( !sortedLayers.contains( dep ) )
{
resolved = false;
break;
}
}
if ( resolved ) // dependencies for this layer are resolved
{
sortedLayers << idToSort;
mSortedLayerNodes << node;
mSortedLayerIds << idToSort;
it = layersToSort.erase( it ); // erase and go to the next
mHasCycle = false;
}
else
{
it++;
}
}
}
}

QgsLayerDefinition::DependencySorter::DependencySorter( QDomDocument doc ) :
mHasCycle( false ), mHasMissingDependency( false )
{
init( doc );
}

QgsLayerDefinition::DependencySorter::DependencySorter( const QString& fileName ) :
mHasCycle( false ), mHasMissingDependency( false )
{
QDomDocument doc;
QFile pFile( fileName );
pFile.open( QIODevice::ReadOnly );
doc.setContent( &pFile );
init( doc );
}


36 changes: 36 additions & 0 deletions src/core/qgslayerdefinition.h
Expand Up @@ -21,6 +21,42 @@ class CORE_EXPORT QgsLayerDefinition
static bool exportLayerDefinition( QString path, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage );
/** Export the selected layer tree nodes to a QLR-XML document */
static bool exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage, const QString& relativeBasePath = QString::null );

/**
* Class used to work with layer dependencies stored in a XML project or layer definition file
*/
class CORE_EXPORT DependencySorter
{
public:
/** Constructor
* @param doc The XML document containing maplayer elements
*/
DependencySorter( QDomDocument doc );

/** Constructor
* @param fileName The filename where the XML document is stored
*/
DependencySorter( const QString& fileName );

/** Get the layer nodes in an order where they can be loaded incrementally without dependency break */
QVector<QDomNode> sortedLayerNodes() const { return mSortedLayerNodes; }

/** Get the layer IDs in an order where they can be loaded incrementally without dependency break */
QStringList sortedLayerIds() const { return mSortedLayerIds; }

/** Whether some cyclic dependency has been detected */
bool hasCycle() const { return mHasCycle; }

/** Whether some dependency is missing */
bool hasMissingDependency() const { return mHasMissingDependency; }

private:
QVector<QDomNode> mSortedLayerNodes;
QStringList mSortedLayerIds;
bool mHasCycle;
bool mHasMissingDependency;
void init( QDomDocument doc );
};
};

#endif // QGSLAYERDEFINITION_H
7 changes: 6 additions & 1 deletion src/core/qgsmaplayer.cpp
Expand Up @@ -47,6 +47,7 @@
#include "qgsrectangle.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgsmaplayerregistry.h"


QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type,
Expand Down Expand Up @@ -788,7 +789,7 @@ QDomDocument QgsMapLayer::asLayerDefinition( const QList<QgsMapLayer *>& layers,
return doc;
}

QList<QgsMapLayer*> QgsMapLayer::fromLayerDefinition( QDomDocument& document )
QList<QgsMapLayer*> QgsMapLayer::fromLayerDefinition( QDomDocument& document, bool addToRegistry, bool addToLegend )
{
QList<QgsMapLayer*> layers;
QDomNodeList layernodes = document.elementsByTagName( "maplayer" );
Expand Down Expand Up @@ -820,7 +821,11 @@ QList<QgsMapLayer*> QgsMapLayer::fromLayerDefinition( QDomDocument& document )

bool ok = layer->readLayerXML( layerElem );
if ( ok )
{
layers << layer;
if ( addToRegistry )
QgsMapLayerRegistry::instance()->addMapLayer( layer, addToLegend );
}
}
return layers;
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsmaplayer.h
Expand Up @@ -333,7 +333,7 @@ class CORE_EXPORT QgsMapLayer : public QObject

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

/** Set a custom property for layer. Properties are stored in a map and saved in project file. */
Expand Down
14 changes: 11 additions & 3 deletions src/core/qgsproject.cpp
Expand Up @@ -35,6 +35,7 @@
#include "qgsrelationmanager.h"
#include "qgsvectorlayer.h"
#include "qgsvisibilitypresetcollection.h"
#include "qgslayerdefinition.h"

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

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

// order layers based on their dependencies
QgsLayerDefinition::DependencySorter depSorter( doc );
if ( depSorter.hasCycle() || depSorter.hasMissingDependency() )
return qMakePair( false, QList<QDomNode>() );

QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();

// Collect vector layers with joins.
// They need to refresh join caches and symbology infos after all layers are loaded
QList< QPair< QgsVectorLayer*, QDomElement > > vLayerList;

for ( int i = 0; i < nl.count(); i++ )
int i = 0;
foreach ( QDomNode node, sortedLayerNodes )
{
QDomNode node = nl.item( i );
QDomElement element = node.toElement();

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

// Update field map of layers with joins and create join caches if necessary
Expand Down
5 changes: 5 additions & 0 deletions src/core/qgsvectordataprovider.cpp
Expand Up @@ -548,4 +548,9 @@ void QgsVectorDataProvider::pushError( const QString& msg )
mErrors << msg;
}

QSet<QString> QgsVectorDataProvider::layerDependencies() const
{
return QSet<QString>();
}

QStringList QgsVectorDataProvider::smEncodings;
5 changes: 5 additions & 0 deletions src/core/qgsvectordataprovider.h
Expand Up @@ -383,6 +383,11 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
emit dataChanged();
}

/**
* 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;

protected:
void clearMinMaxCache();
void fillMinMaxCache();
Expand Down

0 comments on commit f351715

Please sign in to comment.