QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 "qgsvectortilelayer.h"
32 #include "qgsapplication.h"
33 #include "qgsmaplayerfactory.h"
34 #include "qgsmeshlayer.h"
35 #include "qgspointcloudlayer.h"
36 #include "qgsannotationlayer.h"
37 
38 bool QgsLayerDefinition::loadLayerDefinition( const QString &path, QgsProject *project, QgsLayerTreeGroup *rootGroup, QString &errorMessage )
39 {
40  QFile file( path );
41  if ( !file.open( QIODevice::ReadOnly ) )
42  {
43  errorMessage = QStringLiteral( "Can not open file" );
44  return false;
45  }
46 
47  QDomDocument doc;
48  QString message;
49  if ( !doc.setContent( &file, &message ) )
50  {
51  errorMessage = message;
52  return false;
53  }
54 
55  QFileInfo fileinfo( file );
56  QDir::setCurrent( fileinfo.absoluteDir().path() );
57 
58  QgsReadWriteContext context;
59  context.setPathResolver( QgsPathResolver( path ) );
60  context.setProjectTranslator( project );
61 
62  return loadLayerDefinition( doc, project, rootGroup, errorMessage, context );
63 }
64 
65 bool QgsLayerDefinition::loadLayerDefinition( QDomDocument doc, QgsProject *project, QgsLayerTreeGroup *rootGroup, QString &errorMessage, QgsReadWriteContext &context )
66 {
67  errorMessage.clear();
68 
70 
71  // reorder maplayer nodes based on dependencies
72  // dependencies have to be resolved before IDs get changed
73  DependencySorter depSorter( doc );
74  if ( !depSorter.hasMissingDependency() )
75  {
76  QVector<QDomNode> sortedLayerNodes = depSorter.sortedLayerNodes();
77  QVector<QDomNode> clonedSorted;
78  const auto constSortedLayerNodes = sortedLayerNodes;
79  for ( const QDomNode &node : constSortedLayerNodes )
80  {
81  clonedSorted << node.cloneNode();
82  }
83  QDomNode layersNode = doc.elementsByTagName( QStringLiteral( "maplayers" ) ).at( 0 );
84  // replace old children with new ones
85  QDomNode childNode = layersNode.firstChild();
86  for ( int i = 0; ! childNode.isNull(); i++ )
87  {
88  layersNode.replaceChild( clonedSorted.at( i ), childNode );
89  childNode = childNode.nextSibling();
90  }
91  }
92  // if a dependency is missing, we still try to load layers, since dependencies may already be loaded
93 
94  // IDs of layers should be changed otherwise we may have more then one layer with the same id
95  // We have to replace the IDs before we load them because it's too late once they are loaded
96  const QDomNodeList treeLayerNodes = doc.elementsByTagName( QStringLiteral( "layer-tree-layer" ) );
97  QDomNode treeLayerNode = treeLayerNodes.at( 0 );
98  for ( int i = 0; ! treeLayerNode.isNull(); ++i )
99  {
100  treeLayerNode = treeLayerNodes.at( i );
101  QDomElement treeLayerElem = treeLayerNode.toElement();
102  QString oldid = treeLayerElem.attribute( QStringLiteral( "id" ) );
103  QString layername = treeLayerElem.attribute( QStringLiteral( "name" ) );
104  QString newid = QgsMapLayer::generateId( layername );
105  treeLayerElem.setAttribute( QStringLiteral( "id" ), newid );
106 
107  // Replace IDs for map layers
108  const QDomNodeList ids = doc.elementsByTagName( QStringLiteral( "id" ) );
109  QDomNode idnode = ids.at( 0 );
110  for ( int j = 0; ! idnode.isNull() ; ++j )
111  {
112  idnode = ids.at( j );
113  QDomElement idElem = idnode.toElement();
114  if ( idElem.text() == oldid )
115  {
116  idElem.firstChild().setNodeValue( newid );
117  }
118  }
119 
120  // change layer IDs for vector joins
121  const QDomNodeList vectorJoinNodes = doc.elementsByTagName( QStringLiteral( "join" ) ); // TODO: Find a better way of searching for vectorjoins, there might be other <join> elements within the project.
122  for ( int j = 0; j < vectorJoinNodes.size(); ++j )
123  {
124  QDomNode joinNode = vectorJoinNodes.at( j );
125  QDomElement joinElement = joinNode.toElement();
126  if ( joinElement.attribute( QStringLiteral( "joinLayerId" ) ) == oldid )
127  {
128  joinNode.toElement().setAttribute( QStringLiteral( "joinLayerId" ), newid );
129  }
130  }
131 
132  // change IDs of dependencies
133  const QDomNodeList dataDeps = doc.elementsByTagName( QStringLiteral( "dataDependencies" ) );
134  for ( int i = 0; i < dataDeps.size(); i++ )
135  {
136  QDomNodeList layers = dataDeps.at( i ).childNodes();
137  for ( int j = 0; j < layers.size(); j++ )
138  {
139  QDomElement elt = layers.at( j ).toElement();
140  if ( elt.attribute( QStringLiteral( "id" ) ) == oldid )
141  {
142  elt.setAttribute( QStringLiteral( "id" ), newid );
143  }
144  }
145  }
146 
147  // Change IDs of widget config values
148  QDomNodeList widgetConfig = doc.elementsByTagName( QStringLiteral( "editWidget" ) );
149  for ( int i = 0; i < widgetConfig.size(); i++ )
150  {
151  QDomNodeList config = widgetConfig.at( i ).childNodes();
152  for ( int j = 0; j < config.size(); j++ )
153  {
154  QDomNodeList optMap = config.at( j ).childNodes();
155  for ( int z = 0; z < optMap.size(); z++ )
156  {
157  QDomNodeList opts = optMap.at( z ).childNodes();
158  for ( int k = 0; k < opts.size(); k++ )
159  {
160  QDomElement opt = opts.at( k ).toElement();
161  if ( opt.attribute( QStringLiteral( "value" ) ) == oldid )
162  {
163  opt.setAttribute( QStringLiteral( "value" ), newid );
164  }
165  }
166  }
167  }
168  }
169  }
170 
171  QDomElement layerTreeElem = doc.documentElement().firstChildElement( QStringLiteral( "layer-tree-group" ) );
172  bool loadInLegend = true;
173  if ( !layerTreeElem.isNull() )
174  {
175  root->readChildrenFromXml( layerTreeElem, context );
176  loadInLegend = false;
177  }
178 
179  QList<QgsMapLayer *> layers = QgsLayerDefinition::loadLayerDefinitionLayersInternal( doc, context, errorMessage );
180 
181  project->addMapLayers( layers, loadInLegend );
182 
183  // Now that all layers are loaded, refresh the vectorjoins to get the joined fields
184  const auto constLayers = layers;
185  for ( QgsMapLayer *layer : constLayers )
186  {
187  if ( QgsVectorLayer *vlayer = qobject_cast< QgsVectorLayer * >( layer ) )
188  {
189  vlayer->resolveReferences( project );
190  }
191  }
192 
193  root->resolveReferences( project );
194 
195  QList<QgsLayerTreeNode *> nodes = root->children();
196  root->abandonChildren();
197  delete root;
198 
199  rootGroup->insertChildNodes( -1, nodes );
200 
201  return true;
202 }
203 
204 bool QgsLayerDefinition::exportLayerDefinition( QString path, const QList<QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage )
205 {
206  if ( !path.endsWith( QLatin1String( ".qlr" ) ) )
207  path = path.append( ".qlr" );
208 
209  QFile file( path );
210 
211  if ( !file.open( QFile::WriteOnly | QFile::Truncate ) )
212  {
213  errorMessage = file.errorString();
214  return false;
215  }
216 
217  QgsReadWriteContext context;
218  bool writeAbsolutePath = QgsProject::instance()->readBoolEntry( QStringLiteral( "Paths" ), QStringLiteral( "/Absolute" ), false );
219  context.setPathResolver( QgsPathResolver( writeAbsolutePath ? QString() : path ) );
220 
221  QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
222  if ( !exportLayerDefinition( doc, selectedTreeNodes, errorMessage, context ) )
223  return false;
224 
225  QTextStream qlayerstream( &file );
226  doc.save( qlayerstream, 2 );
227  return true;
228 }
229 
230 bool QgsLayerDefinition::exportLayerDefinition( QDomDocument doc, const QList<QgsLayerTreeNode *> &selectedTreeNodes, QString &errorMessage, const QgsReadWriteContext &context )
231 {
232  Q_UNUSED( errorMessage )
233  QDomElement qgiselm = doc.createElement( QStringLiteral( "qlr" ) );
234  doc.appendChild( qgiselm );
235  QList<QgsLayerTreeNode *> nodes = selectedTreeNodes;
237  const auto constNodes = nodes;
238  for ( QgsLayerTreeNode *node : constNodes )
239  {
240  QgsLayerTreeNode *newnode = node->clone();
241  root->addChildNode( newnode );
242  }
243  root->writeXml( qgiselm, context );
244 
245  QDomElement layerselm = doc.createElement( QStringLiteral( "maplayers" ) );
246  QList<QgsLayerTreeLayer *> layers = root->findLayers();
247  const auto constLayers = layers;
248  for ( QgsLayerTreeLayer *layer : constLayers )
249  {
250  if ( ! layer->layer() )
251  {
252  QgsDebugMsgLevel( QStringLiteral( "Not a valid map layer: skipping %1" ).arg( layer->name( ) ), 4 );
253  continue;
254  }
255  QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
256  layer->layer()->writeLayerXml( layerelm, doc, context );
257  layerselm.appendChild( layerelm );
258  }
259  qgiselm.appendChild( layerselm );
260  return true;
261 }
262 
263 QDomDocument QgsLayerDefinition::exportLayerDefinitionLayers( const QList<QgsMapLayer *> &layers, const QgsReadWriteContext &context )
264 {
265  QDomDocument doc( QStringLiteral( "qgis-layer-definition" ) );
266  QDomElement qgiselm = doc.createElement( QStringLiteral( "qlr" ) );
267  doc.appendChild( qgiselm );
268  QDomElement layerselm = doc.createElement( QStringLiteral( "maplayers" ) );
269  const auto constLayers = layers;
270  for ( QgsMapLayer *layer : constLayers )
271  {
272  QDomElement layerelm = doc.createElement( QStringLiteral( "maplayer" ) );
273  layer->writeLayerXml( layerelm, doc, context );
274  layerselm.appendChild( layerelm );
275  }
276  qgiselm.appendChild( layerselm );
277  return doc;
278 }
279 
280 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( QDomDocument &document, QgsReadWriteContext &context )
281 {
282  QString errorMessage;
283  return loadLayerDefinitionLayersInternal( document, context, errorMessage );
284 }
285 
286 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayersInternal( QDomDocument &document, QgsReadWriteContext &context, QString &errorMessage )
287 {
288  QList<QgsMapLayer *> layers;
289  QDomElement layerElem = document.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
290  // For QLR:
291  if ( layerElem.isNull() )
292  {
293  layerElem = document.documentElement().firstChildElement( QStringLiteral( "maplayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
294  }
295 
296  while ( ! layerElem.isNull() )
297  {
298  const QString type = layerElem.attribute( QStringLiteral( "type" ) );
299  QgsMapLayer *layer = nullptr;
300 
301  bool ok = false;
302  const QgsMapLayerType layerType = QgsMapLayerFactory::typeFromString( type, ok );
303  if ( ok )
304  {
305  switch ( layerType )
306  {
308  layer = new QgsVectorLayer();
309  break;
310 
312  layer = new QgsRasterLayer();
313  break;
314 
316  {
317  QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
319  break;
320  }
321 
323  layer = new QgsMeshLayer();
324  break;
325 
327  layer = new QgsVectorTileLayer;
328  break;
329 
331  layer = new QgsPointCloudLayer();
332  break;
333 
335  break;
336  }
337  }
338 
339  if ( layer )
340  {
341  // always add the layer, even if the source is invalid -- this allows users to fix the source
342  // at a later stage and still retain all the layer properties intact
343  layer->readLayerXml( layerElem, context );
344  layers << layer;
345  }
346  else
347  {
348  errorMessage = QObject::tr( "Unsupported layer type: %1" ).arg( type );
349  }
350  layerElem = layerElem.nextSiblingElement( QStringLiteral( "maplayer" ) );
351  }
352  return layers;
353 }
354 
355 QList<QgsMapLayer *> QgsLayerDefinition::loadLayerDefinitionLayers( const QString &qlrfile )
356 {
357  QFile file( qlrfile );
358  if ( !file.open( QIODevice::ReadOnly ) )
359  {
360  QgsDebugMsg( QStringLiteral( "Can't open file" ) );
361  return QList<QgsMapLayer *>();
362  }
363 
364  QDomDocument doc;
365  if ( !doc.setContent( &file ) )
366  {
367  QgsDebugMsg( QStringLiteral( "Can't set content" ) );
368  return QList<QgsMapLayer *>();
369  }
370 
371  QgsReadWriteContext context;
372  context.setPathResolver( QgsPathResolver( qlrfile ) );
373  //no project translator defined here
374  return QgsLayerDefinition::loadLayerDefinitionLayers( doc, context );
375 }
376 
377 void QgsLayerDefinition::DependencySorter::init( const QDomDocument &doc )
378 {
379  // Determine a loading order of layers based on a graph of dependencies
380  QMap< QString, QVector< QString > > dependencies;
381  QStringList sortedLayers;
382  QList< QPair<QString, QDomNode> > layersToSort;
383  QStringList layerIds;
384 
385  QDomElement layerElem = doc.documentElement().firstChildElement( QStringLiteral( "projectlayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
386  // For QLR:
387  if ( layerElem.isNull() )
388  {
389  layerElem = doc.documentElement().firstChildElement( QStringLiteral( "maplayers" ) ).firstChildElement( QStringLiteral( "maplayer" ) );
390  }
391  // For tests (I don't know if there is a real use case for such a document except for test_qgslayerdefinition.py)
392  if ( layerElem.isNull() )
393  {
394  layerElem = doc.documentElement().firstChildElement( QStringLiteral( "maplayer" ) );
395  }
396 
397  const QDomElement &firstElement { layerElem };
398 
399  QVector<QString> deps; //avoid expensive allocation for list for every iteration
400  while ( !layerElem.isNull() )
401  {
402  deps.resize( 0 ); // preserve capacity - don't use clear
403 
404  QString id = layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text();
405  layerIds << id;
406 
407  // dependencies for this layer
408  QDomElement layerDependenciesElem = layerElem.firstChildElement( QStringLiteral( "layerDependencies" ) );
409  if ( !layerDependenciesElem.isNull() )
410  {
411  QDomNodeList dependencyList = layerDependenciesElem.elementsByTagName( QStringLiteral( "layer" ) );
412  for ( int j = 0; j < dependencyList.size(); ++j )
413  {
414  QDomElement depElem = dependencyList.at( j ).toElement();
415  deps << depElem.attribute( QStringLiteral( "id" ) );
416  }
417  }
418  dependencies[id] = deps;
419 
420  if ( deps.empty() )
421  {
422  sortedLayers << id;
423  mSortedLayerNodes << layerElem;
424  mSortedLayerIds << id;
425  }
426  else
427  {
428  layersToSort << qMakePair( id, layerElem );
429  }
430  layerElem = layerElem.nextSiblingElement( );
431  }
432 
433  // check that all dependencies are present
434  const auto constDependencies = dependencies;
435  for ( const QVector< QString > &ids : constDependencies )
436  {
437  const auto constIds = ids;
438  for ( const QString &depId : constIds )
439  {
440  if ( !dependencies.contains( depId ) )
441  {
442  // some dependencies are not satisfied
443  mHasMissingDependency = true;
444  layerElem = firstElement;
445  while ( ! layerElem.isNull() )
446  {
447  mSortedLayerNodes << layerElem;
448  layerElem = layerElem.nextSiblingElement( );
449  }
450  mSortedLayerIds = layerIds;
451  return;
452  }
453  }
454  }
455 
456  // cycles should be very rare, since layers with cyclic dependencies may only be created by
457  // manually modifying the project file
458  mHasCycle = false;
459 
460  while ( !layersToSort.empty() && !mHasCycle )
461  {
462  QList< QPair<QString, QDomNode> >::iterator it = layersToSort.begin();
463  while ( it != layersToSort.end() )
464  {
465  QString idToSort = it->first;
466  QDomNode node = it->second;
467  mHasCycle = true;
468  bool resolved = true;
469  const auto deps { dependencies.value( idToSort ) };
470  for ( const QString &dep : deps )
471  {
472  if ( !sortedLayers.contains( dep ) )
473  {
474  resolved = false;
475  break;
476  }
477  }
478  if ( resolved ) // dependencies for this layer are resolved
479  {
480  sortedLayers << idToSort;
481  mSortedLayerNodes << node;
482  mSortedLayerIds << idToSort;
483  it = layersToSort.erase( it ); // erase and go to the next
484  mHasCycle = false;
485  }
486  else
487  {
488  ++it;
489  }
490  }
491  }
492 }
493 
495  : mHasCycle( false )
496  , mHasMissingDependency( false )
497 {
498  init( doc );
499 }
500 
502  : mHasCycle( false )
503  , mHasMissingDependency( false )
504 {
505  QString qgsProjectFile = fileName;
506  QgsProjectArchive archive;
507  if ( fileName.endsWith( QLatin1String( ".qgz" ), Qt::CaseInsensitive ) )
508  {
509  archive.unzip( fileName );
510  qgsProjectFile = archive.projectFile();
511  }
512 
513  QDomDocument doc;
514  QFile pFile( qgsProjectFile );
515  ( void )pFile.open( QIODevice::ReadOnly );
516  ( void )doc.setContent( &pFile );
517  init( doc );
518 }
519 
520 
static QgsPluginLayerRegistry * pluginLayerRegistry()
Returns the application's plugin layer registry, used for managing plugin layer types.
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.
bool hasMissingDependency() const
Whether some dependency is missing.
DependencySorter(const QDomDocument &doc)
Constructor.
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...
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 QList< QgsMapLayer * > loadLayerDefinitionLayers(QDomDocument &document, QgsReadWriteContext &context)
Creates new layers from a layer definition document.
static bool exportLayerDefinition(QString path, const QList< QgsLayerTreeNode * > &selectedTreeNodes, QString &errorMessage)
Export the selected layer tree nodes to a QLR file.
Layer tree group node serves as a container for layers and further groups.
void resolveReferences(const QgsProject *project, bool looseMatching=false) override
Calls resolveReferences() on child tree nodes.
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.
void addChildNode(QgsLayerTreeNode *node)
Append an existing node.
void insertChildNodes(int index, const QList< QgsLayerTreeNode * > &nodes)
Insert existing nodes at specified position.
void readChildrenFromXml(QDomElement &element, const QgsReadWriteContext &context)
Read children from XML and append them to the group.
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes.
Layer tree node points to a map layer.
This class is a base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > abandonChildren()
Removes the childrens, disconnect all the forwarded and external signals and sets their parent to nul...
virtual QgsLayerTreeNode * clone() const =0
Create a copy of the node. Returns new instance.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
static QgsMapLayerType typeFromString(const QString &string, bool &ok)
Returns the map layer type corresponding a string value.
Base class for all map layer types.
Definition: qgsmaplayer.h:70
bool readLayerXml(const QDomElement &layerElement, QgsReadWriteContext &context, QgsMapLayer::ReadFlags flags=QgsMapLayer::ReadFlags())
Sets state from DOM document.
static QString generateId(const QString &layerName)
Generates an unique identifier for this layer, the generate ID is prefixed by layerName.
Represents a mesh layer supporting display of data on structured or unstructured meshes.
Definition: qgsmeshlayer.h:95
Resolves relative paths into absolute paths and vice versa.
QgsPluginLayer * createLayer(const QString &typeName, const QString &uri=QString())
Returns new layer if corresponding plugin has been found else returns nullptr.
Represents a map layer supporting display of point clouds.
Class allowing to manage the zip/unzip actions on project file.
Definition: qgsarchive.h:116
QString projectFile() const
Returns the current .qgs project file or an empty string if there's none.
Definition: qgsarchive.cpp:129
bool unzip(const QString &zipFilename) override
Clear the current content of this archive and unzip.
Definition: qgsarchive.cpp:142
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer * > &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:467
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=nullptr) const
Reads a boolean from the specified scope and key.
Represents a raster layer.
The class is used as a container of context for various read/write operations on other objects.
void setProjectTranslator(QgsProjectTranslator *projectTranslator)
Sets the project translator.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
Represents a vector layer which manages a vector based data sets.
Implements a map layer that is dedicated to rendering of vector tiles.
QgsMapLayerType
Types of layers that can be added to a map.
Definition: qgis.h:46
@ PointCloudLayer
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QString & typeName