QGIS API Documentation  3.0.2-Girona (307d082)
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 "qgslayerdefinition.h"
22 #include "qgslayertree.h"
23 #include "qgslogger.h"
24 #include "qgsmaplayer.h"
25 #include "qgspathresolver.h"
26 #include "qgspluginlayer.h"
27 #include "qgspluginlayerregistry.h"
28 #include "qgsproject.h"
29 #include "qgsrasterlayer.h"
30 #include "qgsreadwritecontext.h"
31 #include "qgsvectorlayer.h"
32 
33 bool QgsLayerDefinition::loadLayerDefinition( const QString &path, QgsProject *project, QgsLayerTreeGroup *rootGroup, QString &errorMessage )
34 {
35  QFile file( path );
36  if ( !file.open( QIODevice::ReadOnly ) )
37  {
38  errorMessage = QStringLiteral( "Can not open file" );
39  return false;
40  }
41 
42  QDomDocument doc;
43  QString message;
44  if ( !doc.setContent( &file, &message ) )
45  {
46  errorMessage = message;
47  return false;
48  }
49 
50  QFileInfo fileinfo( file );
51  QDir::setCurrent( fileinfo.absoluteDir().path() );
52 
53  QgsReadWriteContext context;
54  context.setPathResolver( QgsPathResolver( path ) );
55 
56  return loadLayerDefinition( doc, project, rootGroup, errorMessage, context );
57 }
58 
59 bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsProject *project, QgsLayerTreeGroup *rootGroup, QString &errorMessage, const QgsReadWriteContext &context )
60 {
61  Q_UNUSED( errorMessage );
62 
64 
65  // reorder maplayer nodes based on dependencies
66  // dependencies have to be resolved before IDs get changed
67  DependencySorter depSorter( doc );
68  if ( !depSorter.hasMissingDependency() )
69  {
70  QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
71  QVector<QDomNode> clonedSorted;
72  Q_FOREACH ( const QDomNode &node, sortedLayerNodes )
73  {
74  clonedSorted << node.cloneNode();
75  }
76  QDomNode layersNode = doc.elementsByTagName( QStringLiteral( "maplayers" ) ).at( 0 );
77  // replace old children with new ones
78  QDomNodeList childNodes = layersNode.childNodes();
79  for ( int i = 0; i < childNodes.size(); i++ )
80  {
81  layersNode.replaceChild( clonedSorted.at( i ), childNodes.at( i ) );
82  }
83  }
84  // if a dependency is missing, we still try to load layers, since dependencies may already be loaded
85 
86  // IDs of layers should be changed otherwise we may have more then one layer with the same id
87  // We have to replace the IDs before we load them because it's too late once they are loaded
88  QDomNodeList ids = doc.elementsByTagName( QStringLiteral( "id" ) );
89  for ( int i = 0; i < ids.size(); ++i )
90  {
91  QDomNode idnode = ids.at( i );
92  QDomElement idElem = idnode.toElement();
93  QString oldid = idElem.text();
94  // Strip the date part because we will replace it.
95  QString layername = oldid.left( oldid.length() - 17 );
96  QDateTime dt = QDateTime::currentDateTime();
97  QString newid = layername + dt.toString( QStringLiteral( "yyyyMMddhhmmsszzz" ) ) + QString::number( qrand() );
98  idElem.firstChild().setNodeValue( newid );
99  QDomNodeList treeLayerNodes = doc.elementsByTagName( QStringLiteral( "layer-tree-layer" ) );
100 
101  for ( int i = 0; i < treeLayerNodes.count(); ++i )
102  {
103  QDomNode layerNode = treeLayerNodes.at( i );
104  QDomElement layerElem = layerNode.toElement();
105  if ( layerElem.attribute( QStringLiteral( "id" ) ) == oldid )
106  {
107  layerNode.toElement().setAttribute( QStringLiteral( "id" ), newid );
108  }
109  }
110 
111  // change layer IDs for vector joins
112  QDomNodeList vectorJoinNodes = doc.elementsByTagName( QStringLiteral( "join" ) ); // TODO: Find a better way of searching for vectorjoins, there might be other <join> elements within the project.
113  for ( int j = 0; j < vectorJoinNodes.size(); ++j )
114  {
115  QDomNode joinNode = vectorJoinNodes.at( j );
116  QDomElement joinElement = joinNode.toElement();
117  if ( joinElement.attribute( QStringLiteral( "joinLayerId" ) ) == oldid )
118  {
119  joinNode.toElement().setAttribute( QStringLiteral( "joinLayerId" ), newid );
120  }
121  }
122 
123  // change IDs of dependencies
124  QDomNodeList dataDeps = doc.elementsByTagName( QStringLiteral( "dataDependencies" ) );
125  for ( int i = 0; i < dataDeps.size(); i++ )
126  {
127  QDomNodeList layers = dataDeps.at( i ).childNodes();
128  for ( int j = 0; j < layers.size(); j++ )
129  {
130  QDomElement elt = layers.at( j ).toElement();
131  if ( elt.attribute( QStringLiteral( "id" ) ) == oldid )
132  {
133  elt.setAttribute( QStringLiteral( "id" ), newid );
134  }
135  }
136  }
137 
138  }
139 
140  QDomElement layerTreeElem = doc.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
141  bool loadInLegend = true;
142  if ( !layerTreeElem.isNull() )
143  {
144  root->readChildrenFromXml( layerTreeElem, context );
145  loadInLegend = false;
146  }
147 
148  QList<QgsMapLayer *> layers = QgsLayerDefinition::loadLayerDefinitionLayers( doc, context );
149 
150  project->addMapLayers( layers, loadInLegend );
151 
152  // Now that all layers are loaded, refresh the vectorjoins to get the joined fields
153  Q_FOREACH ( QgsMapLayer *layer, layers )
154  {
155  if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) )
156  {
157  vlayer->resolveReferences( project );
158  }
159  }
160 
161  root->resolveReferences( project );
162 
163  QList<QgsLayerTreeNode *> nodes = root->children();
164  Q_FOREACH ( QgsLayerTreeNode *node, nodes )
165  root->takeChild( node );
166  delete root;
167 
168  rootGroup->insertChildNodes( -1, nodes );
169 
170  return true;
171 
172 }
173 
174 bool QgsLayerDefinition::exportLayerDefinition( QString path, const QList<QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage )
175 {
176  if ( !path.endsWith( QLatin1String( ".qlr" ) ) )
177  path = path.append( ".qlr" );
178 
179  QFile file( path );
180 
181  if ( !file.open( QFile::WriteOnly | QFile::Truncate ) )
182  {
183  errorMessage = file.errorString();
184  return false;
185  }
186 
187  QgsReadWriteContext context;
188  context.setPathResolver( QgsPathResolver( path ) );
189 
190  QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
191  if ( !exportLayerDefinition( doc, selectedTreeNodes, errorMessage, context ) )
192  return false;
193 
194  QTextStream qlayerstream( &file );
195  doc.save( qlayerstream, 2 );
196  return true;
197 }
198 
199 bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage, const QgsReadWriteContext &context )
200 {
201  Q_UNUSED( errorMessage );
202  QDomElement qgiselm = doc.createElement( QStringLiteral( "qlr" ) );
203  doc.appendChild( qgiselm );
204  QList<QgsLayerTreeNode *> nodes = selectedTreeNodes;
206  Q_FOREACH ( QgsLayerTreeNode *node, nodes )
207  {
208  QgsLayerTreeNode *newnode = node->clone();
209  root->addChildNode( newnode );
210  }
211  root->writeXml( qgiselm, context );
212 
213  QDomElement layerselm = doc.createElement( QStringLiteral( "maplayers" ) );
214  QList<QgsLayerTreeLayer *> layers = root->findLayers();
215  Q_FOREACH ( QgsLayerTreeLayer *layer, layers )
216  {
217  QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
218  layer->layer()->writeLayerXml( layerelm, doc, context );
219  layerselm.appendChild( layerelm );
220  }
221  qgiselm.appendChild( layerselm );
222  return true;
223 }
224 
225 QDomDocument QgsLayerDefinition::exportLayerDefinitionLayers( const QList<QgsMapLayer *> &layers, const QgsReadWriteContext &context )
226 {
227  QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
228  QDomElement qgiselm = doc.createElement( QStringLiteral( "qlr" ) );
229  doc.appendChild( qgiselm );
230  QDomElement layerselm = doc.createElement( QStringLiteral( "maplayers" ) );
231  Q_FOREACH ( QgsMapLayer *layer, layers )
232  {
233  QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
234  layer->writeLayerXml( layerelm, doc, context );
235  layerselm.appendChild( layerelm );
236  }
237  qgiselm.appendChild( layerselm );
238  return doc;
239 }
240 
241 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( QDomDocument &document, const QgsReadWriteContext &context )
242 {
243  QList<QgsMapLayer *> layers;
244  QDomNodeList layernodes = document.elementsByTagName( QStringLiteral( "maplayer" ) );
245  for ( int i = 0; i < layernodes.size(); ++i )
246  {
247  QDomNode layernode = layernodes.at( i );
248  QDomElement layerElem = layernode.toElement();
249 
250  QString type = layerElem.attribute( QStringLiteral( "type" ) );
251  QgsDebugMsg( type );
252  QgsMapLayer *layer = nullptr;
253 
254  if ( type == QLatin1String( "vector" ) )
255  {
256  layer = new QgsVectorLayer;
257  }
258  else if ( type == QLatin1String( "raster" ) )
259  {
260  layer = new QgsRasterLayer;
261  }
262  else if ( type == QLatin1String( "plugin" ) )
263  {
264  QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
265  layer = QgsApplication::pluginLayerRegistry()->createLayer( typeName );
266  }
267 
268  if ( !layer )
269  continue;
270 
271  if ( layer->readLayerXml( layerElem, context ) )
272  {
273  layers << layer;
274  }
275  }
276  return layers;
277 }
278 
279 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( const QString &qlrfile )
280 {
281  QFile file( qlrfile );
282  if ( !file.open( QIODevice::ReadOnly ) )
283  {
284  QgsDebugMsg( "Can't open file" );
285  return QList<QgsMapLayer *>();
286  }
287 
288  QDomDocument doc;
289  if ( !doc.setContent( &file ) )
290  {
291  QgsDebugMsg( "Can't set content" );
292  return QList<QgsMapLayer *>();
293  }
294 
295  QgsReadWriteContext context;
296  context.setPathResolver( QgsPathResolver( qlrfile ) );
297  return QgsLayerDefinition::loadLayerDefinitionLayers( doc, context );
298 }
299 
300 
301 void QgsLayerDefinition::DependencySorter::init( const QDomDocument &doc )
302 {
303  // Determine a loading order of layers based on a graph of dependencies
304  QMap< QString, QVector< QString > > dependencies;
305  QStringList sortedLayers;
306  QList< QPair<QString, QDomNode> > layersToSort;
307  QStringList layerIds;
308 
309  QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "maplayer" ) );
310  layerIds.reserve( nl.count() );
311  QVector<QString> deps; //avoid expensive allocation for list for every iteration
312  for ( int i = 0; i < nl.count(); i++ )
313  {
314  deps.resize( 0 ); // preserve capacity - don't use clear
315  QDomNode node = nl.item( i );
316 
317  QString id = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
318  layerIds << id;
319 
320  // dependencies for this layer
321  QDomElement layerDependenciesElem = node.firstChildElement( QStringLiteral( "layerDependencies" ) );
322  if ( !layerDependenciesElem.isNull() )
323  {
324  QDomNodeList dependencyList = layerDependenciesElem.elementsByTagName( QStringLiteral( "layer" ) );
325  for ( int j = 0; j < dependencyList.size(); ++j )
326  {
327  QDomElement depElem = dependencyList.at( j ).toElement();
328  deps << depElem.attribute( QStringLiteral( "id" ) );
329  }
330  }
331  dependencies[id] = deps;
332 
333  if ( deps.empty() )
334  {
335  sortedLayers << id;
336  mSortedLayerNodes << node;
337  mSortedLayerIds << id;
338  }
339  else
340  layersToSort << qMakePair( id, node );
341  }
342 
343  // check that all dependencies are present
344  Q_FOREACH ( const QVector< QString > &ids, dependencies )
345  {
346  Q_FOREACH ( const QString &depId, ids )
347  {
348  if ( !dependencies.contains( depId ) )
349  {
350  // some dependencies are not satisfied
351  mHasMissingDependency = true;
352  for ( int i = 0; i < nl.size(); i++ )
353  mSortedLayerNodes << nl.at( i );
354  mSortedLayerIds = layerIds;
355  return;
356  }
357  }
358  }
359 
360  // cycles should be very rare, since layers with cyclic dependencies may only be created by
361  // manually modifying the project file
362  mHasCycle = false;
363 
364  while ( !layersToSort.empty() && !mHasCycle )
365  {
366  QList< QPair<QString, QDomNode> >::iterator it = layersToSort.begin();
367  while ( it != layersToSort.end() )
368  {
369  QString idToSort = it->first;
370  QDomNode node = it->second;
371  mHasCycle = true;
372  bool resolved = true;
373  Q_FOREACH ( const QString &dep, dependencies[idToSort] )
374  {
375  if ( !sortedLayers.contains( dep ) )
376  {
377  resolved = false;
378  break;
379  }
380  }
381  if ( resolved ) // dependencies for this layer are resolved
382  {
383  sortedLayers << idToSort;
384  mSortedLayerNodes << node;
385  mSortedLayerIds << idToSort;
386  it = layersToSort.erase( it ); // erase and go to the next
387  mHasCycle = false;
388  }
389  else
390  {
391  ++it;
392  }
393  }
394  }
395 }
396 
398  : mHasCycle( false )
399  , mHasMissingDependency( false )
400 {
401  init( doc );
402 }
403 
405  : mHasCycle( false )
406  , mHasMissingDependency( false )
407 {
408  QDomDocument doc;
409  QFile pFile( fileName );
410  ( void )pFile.open( QIODevice::ReadOnly );
411  ( void )doc.setContent( &pFile );
412  init( doc );
413 }
414 
415 
Layer tree group node serves as a container for layers and further groups.
The class is used as a container of context for various read/write operations on other objects...
void readChildrenFromXml(QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
static QDomDocument exportLayerDefinitionLayers(const QList< QgsMapLayer *> &layers, const QgsReadWriteContext &context)
Returns the given layer as a layer definition document Layer definitions store the data source as wel...
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer *> &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
Base class for all map layer types.
Definition: qgsmaplayer.h:56
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
bool takeChild(QgsLayerTreeNode *node)
Remove a child from a node.
static bool loadLayerDefinition(const QString &path, QgsProject *project, QgsLayerTreeGroup *rootGroup, QString &errorMessage)
Loads the QLR at path into QGIS. New layers are added to given project into layer tree specified by r...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
This class provides qgis with the ability to render raster datasets onto the mapcanvas.
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...
QVector< QDomNode > sortedLayerNodes() const
Get the layer nodes in an order where they can be loaded incrementally without dependency break...
void writeXml(QDomElement &parentElement, const QgsReadWriteContext &context) override
Write group (tree) as XML element <layer-tree-group> and add it to the given parent element...
static QgsPluginLayerRegistry * pluginLayerRegistry()
Returns the application&#39;s plugin layer registry, used for managing plugin layer types.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Calls resolveReferences() on child tree nodes.
DependencySorter(const QDomDocument &doc)
Constructor.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
static QList< QgsMapLayer * > loadLayerDefinitionLayers(QDomDocument &document, const QgsReadWriteContext &context)
Creates new layers from a layer definition document.
This class is a base class for nodes in a layer tree.
Reads and writes project states.
Definition: qgsproject.h:82
void insertChildNodes(int index, const QList< QgsLayerTreeNode *> &nodes)
Insert existing nodes at specified position.
bool readLayerXml(const QDomElement &layerElement, const QgsReadWriteContext &context)
Sets state from Dom document.
bool hasMissingDependency() const
Whether some dependency is missing.
QgsMapLayer * layer() const
static bool exportLayerDefinition(QString path, const QList< QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage)
Export the selected layer tree nodes to a QLR file.
void addChildNode(QgsLayerTreeNode *node)
Append an existing node.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Resolves relative paths into absolute paths and vice versa.
Represents a vector layer which manages a vector based data sets.
bool writeLayerXml(QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context) const
Stores state in Dom node.
QgsPluginLayer * createLayer(const QString &typeName, const QString &uri=QString())
Returns new layer if corresponding plugin has been found else returns a nullptr.
Layer tree node points to a map layer.