QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 #include "qgsapplication.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  context.setProjectTranslator( project );
56 
57  return loadLayerDefinition( doc, project, rootGroup, errorMessage, context );
58 }
59 
60 bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsProject *project, QgsLayerTreeGroup *rootGroup, QString &errorMessage, QgsReadWriteContext &context )
61 {
62  Q_UNUSED( errorMessage )
63 
65 
66  // reorder maplayer nodes based on dependencies
67  // dependencies have to be resolved before IDs get changed
68  DependencySorter depSorter( doc );
69  if ( !depSorter.hasMissingDependency() )
70  {
71  QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
72  QVector<QDomNode> clonedSorted;
73  const auto constSortedLayerNodes = sortedLayerNodes;
74  for ( const QDomNode &node : constSortedLayerNodes )
75  {
76  clonedSorted << node.cloneNode();
77  }
78  QDomNode layersNode = doc.elementsByTagName( QStringLiteral( "maplayers" ) ).at( 0 );
79  // replace old children with new ones
80  QDomNodeList childNodes = layersNode.childNodes();
81  for ( int i = 0; i < childNodes.size(); i++ )
82  {
83  layersNode.replaceChild( clonedSorted.at( i ), childNodes.at( i ) );
84  }
85  }
86  // if a dependency is missing, we still try to load layers, since dependencies may already be loaded
87 
88  // IDs of layers should be changed otherwise we may have more then one layer with the same id
89  // We have to replace the IDs before we load them because it's too late once they are loaded
90  QDomNodeList treeLayerNodes = doc.elementsByTagName( QStringLiteral( "layer-tree-layer" ) );
91  for ( int i = 0; i < treeLayerNodes.size(); ++i )
92  {
93  QDomNode treeLayerNode = treeLayerNodes.at( i );
94  QDomElement treeLayerElem = treeLayerNode.toElement();
95  QString oldid = treeLayerElem.attribute( QStringLiteral( "id" ) );
96  QString layername = treeLayerElem.attribute( QStringLiteral( "name" ) );
97  QString newid = QgsMapLayer::generateId( layername );
98  treeLayerElem.setAttribute( QStringLiteral( "id" ), newid );
99 
100  // Replace IDs for map layers
101  QDomNodeList ids = doc.elementsByTagName( QStringLiteral( "id" ) );
102  for ( int i = 0; i < ids.size(); ++i )
103  {
104  QDomNode idnode = ids.at( i );
105  QDomElement idElem = idnode.toElement();
106  if ( idElem.text() == oldid )
107  {
108  idElem.firstChild().setNodeValue( newid );
109  }
110  }
111 
112  // change layer IDs for vector joins
113  QDomNodeList vectorJoinNodes = doc.elementsByTagName( QStringLiteral( "join" ) ); // TODO: Find a better way of searching for vectorjoins, there might be other <join> elements within the project.
114  for ( int j = 0; j < vectorJoinNodes.size(); ++j )
115  {
116  QDomNode joinNode = vectorJoinNodes.at( j );
117  QDomElement joinElement = joinNode.toElement();
118  if ( joinElement.attribute( QStringLiteral( "joinLayerId" ) ) == oldid )
119  {
120  joinNode.toElement().setAttribute( QStringLiteral( "joinLayerId" ), newid );
121  }
122  }
123 
124  // change IDs of dependencies
125  QDomNodeList dataDeps = doc.elementsByTagName( QStringLiteral( "dataDependencies" ) );
126  for ( int i = 0; i < dataDeps.size(); i++ )
127  {
128  QDomNodeList layers = dataDeps.at( i ).childNodes();
129  for ( int j = 0; j < layers.size(); j++ )
130  {
131  QDomElement elt = layers.at( j ).toElement();
132  if ( elt.attribute( QStringLiteral( "id" ) ) == oldid )
133  {
134  elt.setAttribute( QStringLiteral( "id" ), newid );
135  }
136  }
137  }
138 
139  // Change IDs of widget config values
140  QDomNodeList widgetConfig = doc.elementsByTagName( QStringLiteral( "editWidget" ) );
141  for ( int i = 0; i < widgetConfig.size(); i++ )
142  {
143  QDomNodeList config = widgetConfig.at( i ).childNodes();
144  for ( int j = 0; j < config.size(); j++ )
145  {
146  QDomNodeList optMap = config.at( j ).childNodes();
147  for ( int z = 0; z < optMap.size(); z++ )
148  {
149  QDomNodeList opts = optMap.at( z ).childNodes();
150  for ( int k = 0; k < opts.size(); k++ )
151  {
152  QDomElement opt = opts.at( k ).toElement();
153  if ( opt.attribute( QStringLiteral( "value" ) ) == oldid )
154  {
155  opt.setAttribute( QStringLiteral( "value" ), newid );
156  }
157  }
158  }
159  }
160  }
161  }
162 
163  QDomElement layerTreeElem = doc.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
164  bool loadInLegend = true;
165  if ( !layerTreeElem.isNull() )
166  {
167  root->readChildrenFromXml( layerTreeElem, context );
168  loadInLegend = false;
169  }
170 
171  QList<QgsMapLayer *> layers = QgsLayerDefinition::loadLayerDefinitionLayers( doc, context );
172 
173  project->addMapLayers( layers, loadInLegend );
174 
175  // Now that all layers are loaded, refresh the vectorjoins to get the joined fields
176  const auto constLayers = layers;
177  for ( QgsMapLayer *layer : constLayers )
178  {
179  if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) )
180  {
181  vlayer->resolveReferences( project );
182  }
183  }
184 
185  root->resolveReferences( project );
186 
187  QList<QgsLayerTreeNode *> nodes = root->children();
188  const auto constNodes = nodes;
189  for ( QgsLayerTreeNode *node : constNodes )
190  root->takeChild( node );
191  delete root;
192 
193  rootGroup->insertChildNodes( -1, nodes );
194 
195  return true;
196 
197 }
198 
199 bool QgsLayerDefinition::exportLayerDefinition( QString path, const QList<QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage )
200 {
201  if ( !path.endsWith( QLatin1String( ".qlr" ) ) )
202  path = path.append( ".qlr" );
203 
204  QFile file( path );
205 
206  if ( !file.open( QFile::WriteOnly | QFile::Truncate ) )
207  {
208  errorMessage = file.errorString();
209  return false;
210  }
211 
212  QgsReadWriteContext context;
213  context.setPathResolver( QgsPathResolver( path ) );
214 
215  QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
216  if ( !exportLayerDefinition( doc, selectedTreeNodes, errorMessage, context ) )
217  return false;
218 
219  QTextStream qlayerstream( &file );
220  doc.save( qlayerstream, 2 );
221  return true;
222 }
223 
224 bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage, const QgsReadWriteContext &context )
225 {
226  Q_UNUSED( errorMessage )
227  QDomElement qgiselm = doc.createElement( QStringLiteral( "qlr" ) );
228  doc.appendChild( qgiselm );
229  QList<QgsLayerTreeNode *> nodes = selectedTreeNodes;
231  const auto constNodes = nodes;
232  for ( QgsLayerTreeNode *node : constNodes )
233  {
234  QgsLayerTreeNode *newnode = node->clone();
235  root->addChildNode( newnode );
236  }
237  root->writeXml( qgiselm, context );
238 
239  QDomElement layerselm = doc.createElement( QStringLiteral( "maplayers" ) );
240  QList<QgsLayerTreeLayer *> layers = root->findLayers();
241  const auto constLayers = layers;
242  for ( QgsLayerTreeLayer *layer : constLayers )
243  {
244  if ( ! layer->layer() )
245  {
246  QgsDebugMsgLevel( QStringLiteral( "Not a valid map layer: skipping %1" ).arg( layer->name( ) ), 4 );
247  continue;
248  }
249  QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
250  layer->layer()->writeLayerXml( layerelm, doc, context );
251  layerselm.appendChild( layerelm );
252  }
253  qgiselm.appendChild( layerselm );
254  return true;
255 }
256 
257 QDomDocument QgsLayerDefinition::exportLayerDefinitionLayers( const QList<QgsMapLayer *> &layers, const QgsReadWriteContext &context )
258 {
259  QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
260  QDomElement qgiselm = doc.createElement( QStringLiteral( "qlr" ) );
261  doc.appendChild( qgiselm );
262  QDomElement layerselm = doc.createElement( QStringLiteral( "maplayers" ) );
263  const auto constLayers = layers;
264  for ( QgsMapLayer *layer : constLayers )
265  {
266  QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
267  layer->writeLayerXml( layerelm, doc, context );
268  layerselm.appendChild( layerelm );
269  }
270  qgiselm.appendChild( layerselm );
271  return doc;
272 }
273 
274 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( QDomDocument &document, QgsReadWriteContext &context )
275 {
276  QList<QgsMapLayer *> layers;
277  QDomNodeList layernodes = document.elementsByTagName( QStringLiteral( "maplayer" ) );
278  for ( int i = 0; i < layernodes.size(); ++i )
279  {
280  QDomNode layernode = layernodes.at( i );
281  QDomElement layerElem = layernode.toElement();
282 
283  QString type = layerElem.attribute( QStringLiteral( "type" ) );
284  QgsDebugMsg( type );
285  QgsMapLayer *layer = nullptr;
286 
287  if ( type == QLatin1String( "vector" ) )
288  {
289  layer = new QgsVectorLayer( );
290  }
291  else if ( type == QLatin1String( "raster" ) )
292  {
293  layer = new QgsRasterLayer;
294  }
295  else if ( type == QLatin1String( "plugin" ) )
296  {
297  QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
298  layer = QgsApplication::pluginLayerRegistry()->createLayer( typeName );
299  }
300 
301  if ( !layer )
302  continue;
303 
304  // always add the layer, even if the source is invalid -- this allows users to fix the source
305  // at a later stage and still retain all the layer properties intact
306  layer->readLayerXml( layerElem, context );
307  layers << layer;
308  }
309  return layers;
310 }
311 
312 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( const QString &qlrfile )
313 {
314  QFile file( qlrfile );
315  if ( !file.open( QIODevice::ReadOnly ) )
316  {
317  QgsDebugMsg( QStringLiteral( "Can't open file" ) );
318  return QList<QgsMapLayer *>();
319  }
320 
321  QDomDocument doc;
322  if ( !doc.setContent( &file ) )
323  {
324  QgsDebugMsg( QStringLiteral( "Can't set content" ) );
325  return QList<QgsMapLayer *>();
326  }
327 
328  QgsReadWriteContext context;
329  context.setPathResolver( QgsPathResolver( qlrfile ) );
330  //no project translator defined here
331  return QgsLayerDefinition::loadLayerDefinitionLayers( doc, context );
332 }
333 
334 
335 void QgsLayerDefinition::DependencySorter::init( const QDomDocument &doc )
336 {
337  // Determine a loading order of layers based on a graph of dependencies
338  QMap< QString, QVector< QString > > dependencies;
339  QStringList sortedLayers;
340  QList< QPair<QString, QDomNode> > layersToSort;
341  QStringList layerIds;
342 
343  QDomNodeList nl = doc.elementsByTagName( QStringLiteral( "maplayer" ) );
344  layerIds.reserve( nl.count() );
345  QVector<QString> deps; //avoid expensive allocation for list for every iteration
346  for ( int i = 0; i < nl.count(); i++ )
347  {
348  deps.resize( 0 ); // preserve capacity - don't use clear
349  QDomNode node = nl.item( i );
350 
351  QString id = node.namedItem( QStringLiteral( "id" ) ).toElement().text();
352  layerIds << id;
353 
354  // dependencies for this layer
355  QDomElement layerDependenciesElem = node.firstChildElement( QStringLiteral( "layerDependencies" ) );
356  if ( !layerDependenciesElem.isNull() )
357  {
358  QDomNodeList dependencyList = layerDependenciesElem.elementsByTagName( QStringLiteral( "layer" ) );
359  for ( int j = 0; j < dependencyList.size(); ++j )
360  {
361  QDomElement depElem = dependencyList.at( j ).toElement();
362  deps << depElem.attribute( QStringLiteral( "id" ) );
363  }
364  }
365  dependencies[id] = deps;
366 
367  if ( deps.empty() )
368  {
369  sortedLayers << id;
370  mSortedLayerNodes << node;
371  mSortedLayerIds << id;
372  }
373  else
374  layersToSort << qMakePair( id, node );
375  }
376 
377  // check that all dependencies are present
378  const auto constDependencies = dependencies;
379  for ( const QVector< QString > &ids : constDependencies )
380  {
381  const auto constIds = ids;
382  for ( const QString &depId : constIds )
383  {
384  if ( !dependencies.contains( depId ) )
385  {
386  // some dependencies are not satisfied
387  mHasMissingDependency = true;
388  for ( int i = 0; i < nl.size(); i++ )
389  mSortedLayerNodes << nl.at( i );
390  mSortedLayerIds = layerIds;
391  return;
392  }
393  }
394  }
395 
396  // cycles should be very rare, since layers with cyclic dependencies may only be created by
397  // manually modifying the project file
398  mHasCycle = false;
399 
400  while ( !layersToSort.empty() && !mHasCycle )
401  {
402  QList< QPair<QString, QDomNode> >::iterator it = layersToSort.begin();
403  while ( it != layersToSort.end() )
404  {
405  QString idToSort = it->first;
406  QDomNode node = it->second;
407  mHasCycle = true;
408  bool resolved = true;
409  const auto deps { dependencies.value( idToSort ) };
410  for ( const QString &dep : deps )
411  {
412  if ( !sortedLayers.contains( dep ) )
413  {
414  resolved = false;
415  break;
416  }
417  }
418  if ( resolved ) // dependencies for this layer are resolved
419  {
420  sortedLayers << idToSort;
421  mSortedLayerNodes << node;
422  mSortedLayerIds << idToSort;
423  it = layersToSort.erase( it ); // erase and go to the next
424  mHasCycle = false;
425  }
426  else
427  {
428  ++it;
429  }
430  }
431  }
432 }
433 
435  : mHasCycle( false )
436  , mHasMissingDependency( false )
437 {
438  init( doc );
439 }
440 
442  : mHasCycle( false )
443  , mHasMissingDependency( false )
444 {
445  QDomDocument doc;
446  QFile pFile( fileName );
447  ( void )pFile.open( QIODevice::ReadOnly );
448  ( void )doc.setContent( &pFile );
449  init( doc );
450 }
451 
452 
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...
static QString generateId(const QString &layerName)
Generates an unique identifier for this layer, the generate ID is prefixed by layerName.
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:79
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
bool readLayerXml(const QDomElement &layerElement, QgsReadWriteContext &context, QgsMapLayer::ReadFlags flags=nullptr)
Sets state from DOM document.
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...
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()
Gets list of children of the node. Children are owned by the parent.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
const QString & typeName
This class is a base class for nodes in a layer tree.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:89
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.
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.
QgsPluginLayer * createLayer(const QString &typeName, const QString &uri=QString())
Returns new layer if corresponding plugin has been found else returns nullptr.
Layer tree node points to a map layer.