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