QGIS API Documentation  2.14.0-Essen
qgslayerdefinition.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayerdefinition.cpp
3  ---------------------
4  begin : January 2015
5  copyright : (C) 2015 by Nathan Woodrow
6  email : woodrow dot nathan at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include <QDomNode>
16 #include <QFileInfo>
17 #include <QFile>
18 #include <QDir>
19 #include <QTextStream>
20 
21 #include "qgslogger.h"
22 #include "qgsmaplayer.h"
23 #include "qgsvectorlayer.h"
24 #include "qgslayertree.h"
25 #include "qgsmaplayerregistry.h"
26 #include "qgslayerdefinition.h"
27 
28 bool QgsLayerDefinition::loadLayerDefinition( const QString &path, QgsLayerTreeGroup *rootGroup, QString &errorMessage )
29 {
30  QFile file( path );
31  if ( !file.open( QIODevice::ReadOnly ) )
32  {
33  errorMessage = QLatin1String( "Can not open file" );
34  return false;
35  }
36 
37  QDomDocument doc;
38  QString message;
39  if ( !doc.setContent( &file, &message ) )
40  {
41  errorMessage = message;
42  return false;
43  }
44 
45  QFileInfo fileinfo( file );
46  QDir::setCurrent( fileinfo.absoluteDir().path() );
47 
48  return loadLayerDefinition( doc, rootGroup, errorMessage );
49 }
50 
52 {
53  Q_UNUSED( errorMessage );
54 
56 
57  // reorder maplayer nodes based on dependencies
58  // dependencies have to be resolved before IDs get changed
59  DependencySorter depSorter( doc );
60  if ( !depSorter.hasMissingDependency() )
61  {
62  QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
63  QVector<QDomNode> clonedSorted;
64  Q_FOREACH ( const QDomNode& node, sortedLayerNodes )
65  {
66  clonedSorted << node.cloneNode();
67  }
68  QDomNode layersNode = doc.elementsByTagName( "maplayers" ).at( 0 );
69  // replace old children with new ones
70  QDomNodeList childNodes = layersNode.childNodes();
71  for ( int i = 0; i < childNodes.size(); i++ )
72  {
73  layersNode.replaceChild( clonedSorted.at( i ), childNodes.at( i ) );
74  }
75  }
76  // if a dependency is missing, we still try to load layers, since dependencies may already be loaded
77 
78  // IDs of layers should be changed otherwise we may have more then one layer with the same id
79  // We have to replace the IDs before we load them because it's too late once they are loaded
80  QDomNodeList ids = doc.elementsByTagName( "id" );
81  for ( int i = 0; i < ids.size(); ++i )
82  {
83  QDomNode idnode = ids.at( i );
84  QDomElement idElem = idnode.toElement();
85  QString oldid = idElem.text();
86  // Strip the date part because we will replace it.
87  QString layername = oldid.left( oldid.length() - 17 );
89  QString newid = layername + dt.toString( "yyyyMMddhhmmsszzz" ) + QString::number( qrand() );
90  idElem.firstChild().setNodeValue( newid );
91  QDomNodeList treeLayerNodes = doc.elementsByTagName( "layer-tree-layer" );
92 
93  for ( int i = 0; i < treeLayerNodes.count(); ++i )
94  {
95  QDomNode layerNode = treeLayerNodes.at( i );
96  QDomElement layerElem = layerNode.toElement();
97  if ( layerElem.attribute( "id" ) == oldid )
98  {
99  layerNode.toElement().setAttribute( "id", newid );
100  }
101  }
102 
103  // change layer IDs for vector joins
104  QDomNodeList vectorJoinNodes = doc.elementsByTagName( "join" ); // TODO: Find a better way of searching for vectorjoins, there might be other <join> elements within the project.
105  for ( int j = 0; j < vectorJoinNodes.size(); ++j )
106  {
107  QDomNode joinNode = vectorJoinNodes.at( j );
108  QDomElement joinElement = joinNode.toElement();
109  if ( joinElement.attribute( "joinLayerId" ) == oldid )
110  {
111  joinNode.toElement().setAttribute( "joinLayerId", newid );
112  }
113  }
114  }
115 
116  QDomElement layerTreeElem = doc.documentElement().firstChildElement( "layer-tree-group" );
117  bool loadInLegend = true;
118  if ( !layerTreeElem.isNull() )
119  {
120  root->readChildrenFromXML( layerTreeElem );
121  loadInLegend = false;
122  }
123 
124  QList<QgsMapLayer*> layers = QgsMapLayer::fromLayerDefinition( doc, /*addToRegistry*/ true, loadInLegend );
125 
126  // Now that all layers are loaded, refresh the vectorjoins to get the joined fields
127  Q_FOREACH ( QgsMapLayer* layer, layers )
128  {
129  QgsVectorLayer* vlayer = dynamic_cast< QgsVectorLayer * >( layer );
130  if ( vlayer )
131  {
132  vlayer->createJoinCaches();
133  vlayer->updateFields();
134  }
135  }
136 
137  QList<QgsLayerTreeNode*> nodes = root->children();
138  Q_FOREACH ( QgsLayerTreeNode *node, nodes )
139  root->takeChild( node );
140  delete root;
141 
142  rootGroup->insertChildNodes( -1, nodes );
143 
144  return true;
145 
146 }
147 
148 bool QgsLayerDefinition::exportLayerDefinition( QString path, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage )
149 {
150  if ( !path.endsWith( ".qlr" ) )
151  path = path.append( ".qlr" );
152 
153  QFile file( path );
154 
155  if ( !file.open( QFile::WriteOnly | QFile::Truncate ) )
156  {
157  errorMessage = file.errorString();
158  return false;
159  }
160 
161  QFileInfo fileinfo( file );
162 
163  QDomDocument doc( "qgis-layer-definition" );
164  if ( !exportLayerDefinition( doc, selectedTreeNodes, errorMessage, fileinfo.canonicalFilePath() ) )
165  return false;
166 
167  QTextStream qlayerstream( &file );
168  doc.save( qlayerstream, 2 );
169  return true;
170 }
171 
172 bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode*>& selectedTreeNodes, QString &errorMessage, const QString& relativeBasePath )
173 {
174  Q_UNUSED( errorMessage );
175  QDomElement qgiselm = doc.createElement( "qlr" );
176  doc.appendChild( qgiselm );
177  QList<QgsLayerTreeNode*> nodes = selectedTreeNodes;
179  Q_FOREACH ( QgsLayerTreeNode* node, nodes )
180  {
181  QgsLayerTreeNode* newnode = node->clone();
182  root->addChildNode( newnode );
183  }
184  root->writeXML( qgiselm );
185 
186  QDomElement layerselm = doc.createElement( "maplayers" );
187  QList<QgsLayerTreeLayer*> layers = root->findLayers();
188  Q_FOREACH ( QgsLayerTreeLayer* layer, layers )
189  {
190  QDomElement layerelm = doc.createElement( "maplayer" );
191  layer->layer()->writeLayerXML( layerelm, doc, relativeBasePath );
192  layerselm.appendChild( layerelm );
193  }
194  qgiselm.appendChild( layerselm );
195  return true;
196 }
197 
198 void QgsLayerDefinition::DependencySorter::init( const QDomDocument& doc )
199 {
200  // Determine a loading order of layers based on a graph of dependencies
201  QMap< QString, QVector< QString > > dependencies;
202  QStringList sortedLayers;
203  QList< QPair<QString, QDomNode> > layersToSort;
204  QStringList layerIds;
205 
206  QDomNodeList nl = doc.elementsByTagName( "maplayer" );
207  for ( int i = 0; i < nl.count(); i++ )
208  {
209  QVector<QString> deps;
210  QDomNode node = nl.item( i );
211  QDomElement element = node.toElement();
212 
213  QString id = node.namedItem( "id" ).toElement().text();
214  layerIds << id;
215 
216  // dependencies for this layer
217  QDomElement layerDependenciesElem = node.firstChildElement( "layerDependencies" );
218  if ( !layerDependenciesElem.isNull() )
219  {
220  QDomNodeList dependencyList = layerDependenciesElem.elementsByTagName( "layer" );
221  for ( int j = 0; j < dependencyList.size(); ++j )
222  {
223  QDomElement depElem = dependencyList.at( j ).toElement();
224  deps << depElem.attribute( "id" );
225  }
226  }
227  dependencies[id] = deps;
228 
229  if ( deps.empty() )
230  {
231  sortedLayers << id;
232  mSortedLayerNodes << node;
233  mSortedLayerIds << id;
234  }
235  else
236  layersToSort << qMakePair( id, node );
237  }
238 
239  // check that all dependencies are present
240  Q_FOREACH ( const QVector< QString >& ids, dependencies )
241  {
242  Q_FOREACH ( const QString& depId, ids )
243  {
244  if ( !dependencies.contains( depId ) )
245  {
246  // some dependencies are not satisfied
247  mHasMissingDependency = true;
248  for ( int i = 0; i < nl.size(); i++ )
249  mSortedLayerNodes << nl.at( i );
250  mSortedLayerIds = layerIds;
251  return;
252  }
253  }
254  }
255 
256  // cycles should be very rare, since layers with cyclic dependencies may only be created by
257  // manually modifying the project file
258  mHasCycle = false;
259 
260  while ( !layersToSort.empty() && !mHasCycle )
261  {
262  QList< QPair<QString, QDomNode> >::iterator it = layersToSort.begin();
263  while ( it != layersToSort.end() )
264  {
265  QString idToSort = it->first;
266  QDomNode node = it->second;
267  mHasCycle = true;
268  bool resolved = true;
269  Q_FOREACH ( const QString& dep, dependencies[idToSort] )
270  {
271  if ( !sortedLayers.contains( dep ) )
272  {
273  resolved = false;
274  break;
275  }
276  }
277  if ( resolved ) // dependencies for this layer are resolved
278  {
279  sortedLayers << idToSort;
280  mSortedLayerNodes << node;
281  mSortedLayerIds << idToSort;
282  it = layersToSort.erase( it ); // erase and go to the next
283  mHasCycle = false;
284  }
285  else
286  {
287  it++;
288  }
289  }
290  }
291 }
292 
294  mHasCycle( false ), mHasMissingDependency( false )
295 {
296  init( doc );
297 }
298 
300  mHasCycle( false ), mHasMissingDependency( false )
301 {
302  QDomDocument doc;
303  QFile pFile( fileName );
304  ( void )pFile.open( QIODevice::ReadOnly );
305  ( void )doc.setContent( &pFile );
306  init( doc );
307 }
308 
309 
void updateFields()
Assembles mUpdatedFields considering provider fields, joined fields and added fields.
Layer tree group node serves as a container for layers and further groups.
QDomNodeList elementsByTagName(const QString &tagname) const
QString toString(Qt::DateFormat format) const
Base class for all map layer types.
Definition: qgsmaplayer.h:49
QString & append(QChar ch)
QDomNode item(int index) const
void readChildrenFromXML(QDomElement &element)
Read children from XML and append them to the group.
bool contains(const Key &key) const
QDomNode appendChild(const QDomNode &newChild)
bool takeChild(QgsLayerTreeNode *node)
Remove a child from a node.
QString attribute(const QString &name, const QString &defValue) const
QgsMapLayer * layer() const
QString errorString() const
virtual QgsLayerTreeNode * clone() const =0
Create a copy of the node. Returns new instance.
Class used to work with layer dependencies stored in a XML project or layer definition file...
bool contains(const QString &str, Qt::CaseSensitivity cs) const
iterator erase(iterator pos)
QDomElement documentElement() const
QDomNodeList childNodes() const
bool writeLayerXML(QDomElement &layerElement, QDomDocument &document, const QString &relativeBasePath=QString::null)
Stores state in Dom node.
QDomElement toElement() const
DependencySorter(const QDomDocument &doc)
Constructor.
QString canonicalFilePath() const
int count() const
QString number(int n, int base)
bool empty() const
QString text() const
QString path() const
void setAttribute(const QString &name, const QString &value)
static QList< QgsMapLayer * > fromLayerDefinition(QDomDocument &document, bool addToRegistry=false, bool addToLegend=false)
Creates a new layer from a layer defininition document.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes. Searches recursively the whole sub-tree.
QDomNodeList elementsByTagName(const QString &tagname) const
void setNodeValue(const QString &v)
QDir absoluteDir() const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
This class is a base class for nodes in a layer tree.
bool setCurrent(const QString &path)
T & first()
bool hasMissingDependency() const
Whether some dependency is missing.
static bool exportLayerDefinition(QString path, const QList< QgsLayerTreeNode * > &selectedTreeNodes, QString &errorMessage)
Export the selected layer tree nodes to a QLR file.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
virtual void writeXML(QDomElement &parentElement) override
Write group (tree) as XML element <layer-tree-group> and add it to the given parent element...
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position. The nodes must not have a parent yet. The nodes will be owned by this group.
iterator end()
QDomNode namedItem(const QString &name) const
bool isNull() const
const T & at(int i) const
QDateTime currentDateTime()
void save(QTextStream &str, int indent) const
QDomNode firstChild() const
QDomNode replaceChild(const QDomNode &newChild, const QDomNode &oldChild)
QDomNode cloneNode(bool deep) const
QDomElement firstChildElement(const QString &tagName) const
void addChildNode(QgsLayerTreeNode *node)
Append an existing node. The node must not have a parent yet. The node will be owned by this group...
int length() const
QString left(int n) const
QVector< QDomNode > sortedLayerNodes() const
Get the layer nodes in an order where they can be loaded incrementally without dependency break...
int size() const
QDomElement createElement(const QString &tagName)
Represents a vector layer which manages a vector based data sets.
bool empty() const
iterator begin()
static bool loadLayerDefinition(const QString &path, QgsLayerTreeGroup *rootGroup, QString &errorMessage)
Loads the QLR at path into QGIS.
void createJoinCaches()
Caches joined attributes if required (and not already done)
Layer tree node points to a map layer.
QDomNode at(int index) const
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)