QGIS API Documentation  2.99.0-Master (90ae728)
qgslayertreemapcanvasbridge.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayertreemapcanvasbridge.cpp
3  --------------------------------------
4  Date : May 2014
5  Copyright : (C) 2014 by Martin Dobias
6  Email : wonder dot sk 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 
17 
18 #include "qgslayertree.h"
19 #include "qgslayertreeutils.h"
20 #include "qgsmaplayer.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsmapcanvas.h"
23 #include "qgsmapoverviewcanvas.h"
24 #include "qgsproject.h"
25 
27  : QObject( parent )
28  , mRoot( root )
29  , mCanvas( canvas )
30  , mOverviewCanvas( nullptr )
31  , mPendingCanvasUpdate( false )
32  , mHasCustomLayerOrder( false )
33  , mAutoSetupOnFirstLayer( true )
34  , mAutoEnableCrsTransform( true )
35  , mLastLayerCount( !root->findLayers().isEmpty() )
36 {
41 
43 }
44 
46 {
47  setHasCustomLayerOrder( false );
49 }
50 
52 {
53  QStringList order;
54  defaultLayerOrder( mRoot, order );
55  return order;
56 }
57 
59 {
60  if ( QgsLayerTree::isLayer( node ) )
61  {
62  QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
63  order << nodeLayer->layerId();
64  }
65 
66  Q_FOREACH ( QgsLayerTreeNode* child, node->children() )
67  defaultLayerOrder( child, order );
68 }
69 
70 
72 {
73  if ( mHasCustomLayerOrder == state )
74  return;
75 
76  mHasCustomLayerOrder = state;
78 
80 }
81 
82 void QgsLayerTreeMapCanvasBridge::setCustomLayerOrder( const QStringList& order )
83 {
84  if ( mCustomLayerOrder == order )
85  return;
86 
87  // verify that the new order is correct
88  QStringList defOrder( defaultLayerOrder() );
89  QStringList newOrder( order );
90  QStringList sortedNewOrder( order );
91  std::sort( defOrder.begin(), defOrder.end() );
92  std::sort( sortedNewOrder.begin(), sortedNewOrder.end() );
93 
94  if ( defOrder.size() < sortedNewOrder.size() )
95  {
96  // might contain bad layers, but also duplicates
97  QSet<QString> ids( defOrder.toSet() );
98 
99  for ( int i = 0; i < sortedNewOrder.size(); i++ )
100  {
101  if ( !ids.contains( sortedNewOrder[i] ) )
102  {
103  newOrder.removeAll( sortedNewOrder[i] );
104  sortedNewOrder.removeAt( i-- );
105  }
106  }
107  }
108 
109  if ( defOrder != sortedNewOrder )
110  return; // must be permutation of the default order
111 
112  mCustomLayerOrder = newOrder;
114 
115  if ( mHasCustomLayerOrder )
117 }
118 
120 {
121  QList<QgsMapLayer*> canvasLayers, overviewLayers;
122 
123  if ( mHasCustomLayerOrder )
124  {
125  Q_FOREACH ( const QString& layerId, mCustomLayerOrder )
126  {
127  QgsLayerTreeLayer* nodeLayer = mRoot->findLayer( layerId );
128  if ( nodeLayer )
129  {
130  if ( nodeLayer->isVisible() )
131  canvasLayers << nodeLayer->layer();
132  if ( nodeLayer->customProperty( QStringLiteral( "overview" ), 0 ).toInt() )
133  overviewLayers << nodeLayer->layer();
134  }
135  }
136  }
137  else
138  setCanvasLayers( mRoot, canvasLayers, overviewLayers );
139 
140  QList<QgsLayerTreeLayer*> layerNodes = mRoot->findLayers();
141  int currentLayerCount = layerNodes.count();
142  bool firstLayers = mAutoSetupOnFirstLayer && mLastLayerCount == 0 && currentLayerCount != 0;
143 
144  if ( firstLayers )
145  {
146  // also setup destination CRS and map units if the OTF projections are not yet enabled
148  {
149  Q_FOREACH ( QgsLayerTreeLayer* layerNode, layerNodes )
150  {
151  if ( !layerNode->layer() )
152  continue;
153 
154  if ( layerNode->layer()->isSpatial() )
155  {
156  mCanvas->setDestinationCrs( layerNode->layer()->crs() );
157  QgsProject::instance()->setCrs( layerNode->layer()->crs() );
158  mCanvas->setMapUnits( layerNode->layer()->crs().mapUnits() );
159  break;
160  }
161  }
162  }
163  }
164 
165  mCanvas->setLayers( canvasLayers );
166  if ( mOverviewCanvas )
167  mOverviewCanvas->setLayers( overviewLayers );
168 
169  if ( firstLayers )
170  {
171  // if we are moving from zero to non-zero layers, let's zoom to those data
173  }
174 
175  if ( !mFirstCRS.isValid() )
176  {
177  // find out what is the first used CRS in case we may need to turn on OTF projections later
178  Q_FOREACH ( QgsLayerTreeLayer* layerNode, layerNodes )
179  {
180  if ( layerNode->layer() && layerNode->layer()->crs().isValid() )
181  {
182  mFirstCRS = layerNode->layer()->crs();
183  break;
184  }
185  }
186  }
187 
189  {
190  // check whether all layers still have the same CRS
191  Q_FOREACH ( QgsLayerTreeLayer* layerNode, layerNodes )
192  {
193  if ( layerNode->layer() && layerNode->layer()->crs().isValid() && layerNode->layer()->crs() != mFirstCRS )
194  {
198  break;
199  }
200  }
201  }
202 
203  mLastLayerCount = currentLayerCount;
204  if ( currentLayerCount == 0 )
206 
207  mPendingCanvasUpdate = false;
208 
209  emit canvasLayersChanged( canvasLayers );
210 }
211 
212 void QgsLayerTreeMapCanvasBridge::readProject( const QDomDocument& doc )
213 {
214  mFirstCRS = QgsCoordinateReferenceSystem(); // invalidate on project load
215 
216  QDomElement elem = doc.documentElement().firstChildElement( QStringLiteral( "layer-tree-canvas" ) );
217  if ( elem.isNull() )
218  {
219  bool oldEnabled;
220  QStringList oldOrder;
221  if ( QgsLayerTreeUtils::readOldLegendLayerOrder( doc.documentElement().firstChildElement( QStringLiteral( "legend" ) ), oldEnabled, oldOrder ) )
222  {
223  setHasCustomLayerOrder( oldEnabled );
224  setCustomLayerOrder( oldOrder );
225  }
226  return;
227  }
228 
229  QDomElement customOrderElem = elem.firstChildElement( QStringLiteral( "custom-order" ) );
230  if ( !customOrderElem.isNull() )
231  {
232  QStringList order;
233  QDomElement itemElem = customOrderElem.firstChildElement( QStringLiteral( "item" ) );
234  while ( !itemElem.isNull() )
235  {
236  order.append( itemElem.text() );
237  itemElem = itemElem.nextSiblingElement( QStringLiteral( "item" ) );
238  }
239 
240  setHasCustomLayerOrder( customOrderElem.attribute( QStringLiteral( "enabled" ), QString() ).toInt() );
241  setCustomLayerOrder( order );
242  }
243 }
244 
246 {
247  QDomElement elem = doc.createElement( QStringLiteral( "layer-tree-canvas" ) );
248  QDomElement customOrderElem = doc.createElement( QStringLiteral( "custom-order" ) );
249  customOrderElem.setAttribute( QStringLiteral( "enabled" ), mHasCustomLayerOrder ? 1 : 0 );
250 
251  Q_FOREACH ( const QString& layerId, mCustomLayerOrder )
252  {
253  QDomElement itemElem = doc.createElement( QStringLiteral( "item" ) );
254  itemElem.appendChild( doc.createTextNode( layerId ) );
255  customOrderElem.appendChild( itemElem );
256  }
257  elem.appendChild( customOrderElem );
258 
259  doc.documentElement().appendChild( elem );
260 }
261 
262 void QgsLayerTreeMapCanvasBridge::setCanvasLayers( QgsLayerTreeNode *node, QList<QgsMapLayer*> &canvasLayers, QList<QgsMapLayer*>& overviewLayers )
263 {
264  if ( QgsLayerTree::isLayer( node ) )
265  {
266  QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
267  if ( nodeLayer->isVisible() )
268  canvasLayers << nodeLayer->layer();
269  if ( nodeLayer->customProperty( QStringLiteral( "overview" ), 0 ).toInt() )
270  overviewLayers << nodeLayer->layer();
271  }
272 
273  Q_FOREACH ( QgsLayerTreeNode* child, node->children() )
274  setCanvasLayers( child, canvasLayers, overviewLayers );
275 }
276 
278 {
279  if ( mPendingCanvasUpdate )
280  return;
281 
282  mPendingCanvasUpdate = true;
283  QMetaObject::invokeMethod( this, "setCanvasLayers", Qt::QueuedConnection );
284 }
285 
286 void QgsLayerTreeMapCanvasBridge::nodeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
287 {
288  Q_ASSERT( node );
289 
290  // collect layer IDs that have been added in order to put them into custom layer order
291  QStringList layerIds;
292  QList<QgsLayerTreeNode*> children = node->children();
293  for ( int i = indexFrom; i <= indexTo; ++i )
294  {
295  QgsLayerTreeNode* child = children.at( i );
296  if ( QgsLayerTree::isLayer( child ) )
297  {
298  layerIds << QgsLayerTree::toLayer( child )->layerId();
299  }
300  else if ( QgsLayerTree::isGroup( child ) )
301  {
302  Q_FOREACH ( QgsLayerTreeLayer* nodeL, QgsLayerTree::toGroup( child )->findLayers() )
303  layerIds << nodeL->layerId();
304  }
305  }
306 
307  Q_FOREACH ( const QString& layerId, layerIds )
308  {
309  if ( !mCustomLayerOrder.contains( layerId ) )
310  mCustomLayerOrder.append( layerId );
311  }
312 
314 
316 }
317 
319 {
320  // no need to disconnect from removed nodes as they are deleted
321 
322  // check whether the layers are still there, if not, remove them from the layer order!
323  QList<int> toRemove;
324  for ( int i = 0; i < mCustomLayerOrder.count(); ++i )
325  {
327  if ( !node )
328  toRemove << i;
329  }
330  for ( int i = toRemove.count() - 1; i >= 0; --i )
331  mCustomLayerOrder.removeAt( toRemove[i] );
333 
335 }
336 
338 {
340 }
341 
343 {
344  Q_UNUSED( node );
345  if ( key == QLatin1String( "overview" ) )
347 }
348 
static bool readOldLegendLayerOrder(const QDomElement &legendElem, bool &hasCustomOrder, QStringList &order)
Try to load custom layer order from.
Layer tree group node serves as a container for layers and further groups.
void hasCustomLayerOrderChanged(bool)
void setCustomLayerOrder(const QStringList &order)
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
Definition: qgsmaplayer.h:352
void nodeCustomPropertyChanged(QgsLayerTreeNode *node, const QString &key)
QgsLayerTreeMapCanvasBridge(QgsLayerTreeGroup *root, QgsMapCanvas *canvas, QObject *parent=nullptr)
Constructor: does not take ownership of the layer tree nor canvas.
bool hasCrsTransformEnabled() const
returns true if projections are enabled for this layer set
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well) ...
void setCanvasLayers()
force update of canvas layers from the layer tree. Normally this should not be needed to be called...
QString layerId() const
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:72
QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group. No type checking is done - use isGroup() to find out whether this operation is ...
Definition: qgslayertree.h:46
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
sets destination coordinate reference system
void removedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)
Emitted when one or more nodes has been removed from a node within the tree.
void setCrsTransformEnabled(bool enabled)
sets whether to use projections for this layer set
void setLayers(const QList< QgsMapLayer *> &layers)
updates layer set for overview
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set map units (needed by project properties dialog)
void addedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)
Emitted when one or more nodes have been added to a node within the tree.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file...
void nodeAddedChildren(QgsLayerTreeNode *node, int indexFrom, int indexTo)
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the project&#39;s native coordinate reference system.
Definition: qgsproject.cpp:429
QgsCoordinateReferenceSystem crs() const
Returns the layer&#39;s spatial reference system.
This class is a base class for nodes in a layer tree.
QList< QgsLayerTreeNode * > children()
Get list of children of the node. Children are owned by the parent.
bool isLayer(QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:40
void readProject(const QDomDocument &doc)
void canvasLayersChanged(const QList< QgsMapLayer * > &layers)
Emitted when the set of layers (or order of layers) visible in the canvas changes.
QgsMapLayer * layer() const
const QgsMapSettings & mapSettings() const
Get access to properties used for map rendering.
QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer. No type checking is done - use isLayer() to find out whether this operation is ...
Definition: qgslayertree.h:52
void visibilityChanged(QgsLayerTreeNode *node)
Emitted when check state of a node within the tree has been changed.
void setLayers(const QList< QgsMapLayer *> &layers)
Set list of layers that should be shown in the canvas.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:355
void zoomToFullExtent()
Zoom to the full extent of all layers.
This class represents a coordinate reference system (CRS).
void customLayerOrderChanged(const QStringList &order)
QList< QgsLayerTreeLayer * > findLayers() const
Find all layer nodes. Searches recursively the whole sub-tree.
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
void customPropertyChanged(QgsLayerTreeNode *node, const QString &key)
Emitted when a custom property of a node within the tree has been changed or removed.
QgsCoordinateReferenceSystem mFirstCRS
bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:34
Layer tree node points to a map layer.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.