.. highlight:: python
   :linenothreshold: 5


.. testsetup:: legend

    from qgis.core import (
        QgsProject,
        QgsVectorLayer,
    )

    iface = start_qgis()

    # Load the countries layer
    if not QgsProject.instance().mapLayersByName("countries"):
        vlayer = QgsVectorLayer("/usr/share/qgis/resources/data/world_map.gpkg|layerName=countries", "countries", "ogr")
        assert vlayer.isValid()
        QgsProject.instance().addMapLayers([vlayer])

.. _legendpy:

*************************************
Accessing the Table Of Contents (TOC)
*************************************

.. hint:: The code snippets on this page need the following imports if you're outside the pyqgis console:

  .. testcode:: legend

    from qgis.core import (
        QgsProject,
        QgsVectorLayer,
    )

.. only:: html

   .. contents::
      :local:


You can use different classes to access all the loaded layers in the TOC and
use them to retrieve information:

* :class:`QgsProject <qgis.core.QgsProject>`
* :class:`QgsLayerTreeGroup <qgis.core.QgsLayerTreeGroup>`

The QgsProject class
====================

You can use :class:`QgsProject <qgis.core.QgsProject>` to retrieve information
about the TOC and all the layers loaded.

You have to create an ``instance`` of :class:`QgsProject <qgis.core.QgsProject>`
and use its methods to get the loaded layers.

The main method is :meth:`mapLayers() <qgis.core.QgsProject.mapLayers>`. It will
return a dictionary of the loaded layers:


.. testcode:: legend

  layers = QgsProject.instance().mapLayers()
  print(layers)

.. testoutput:: legend

  {'countries_89ae1b0f_f41b_4f42_bca4_caf55ddbe4b6': <QgsVectorLayer: 'countries' (ogr)>}

The dictionary ``keys`` are the unique layer ids while the ``values`` are the
related objects.

It is now straightforward to obtain any other information about the layers:

.. testcode:: legend

  # list of layer names using list comprehension
  l = [layer.name() for layer in QgsProject.instance().mapLayers().values()]
  # dictionary with key = layer name and value = layer object
  layers_list = {}
  for l in QgsProject.instance().mapLayers().values():
    layers_list[l.name()] = l

  print(layers_list)

.. testoutput:: legend

  {'countries': <QgsVectorLayer: 'countries' (ogr)>}


You can also query the TOC using the name of the layer:

.. testcode:: legend

    country_layer = QgsProject.instance().mapLayersByName("countries")[0]

.. note:: A list with all the matching layers is returned, so we index with
  ``[0]`` to get the first layer with this name.


QgsLayerTreeGroup class
=======================

The layer tree is a classical tree structure built of nodes. There are currently
two types of nodes: group nodes (:class:`QgsLayerTreeGroup <qgis.core.QgsLayerTreeGroup>`)
and layer nodes (:class:`QgsLayerTreeLayer <qgis.core.QgsLayerTreeLayer>`).

.. note:: for more information you can read these blog posts of Martin Dobias:
  `Part 1 <https://www.lutraconsulting.co.uk/blogs/qgis-layer-tree-api-part-1/>`_
  `Part 2 <https://www.lutraconsulting.co.uk/blogs/qgis-layer-tree-api-part-2>`_
  `Part 3 <https://www.lutraconsulting.co.uk/blogs/qgis-layer-tree-api-part-3>`_

The project layer tree can be accessed easily with the method :meth:`layerTreeRoot() <qgis.core.QgsProject.layerTreeRoot>`
of the :class:`QgsProject <qgis.core.QgsProject>` class:

.. testcode:: legend

    root = QgsProject.instance().layerTreeRoot()

``root`` is a group node and has *children*:

.. testcode:: legend

    root.children()

A list of direct children is returned. Sub group children should be accessed
from their own direct parent.

We can retrieve one of the children:

.. testcode:: legend

    child0 = root.children()[0]
    print(child0)

.. testoutput:: legend

    <QgsLayerTreeLayer: countries>

Layers can also be retrieved using their (unique) ``id``:

.. testcode:: legend

    ids = root.findLayerIds()
    # access the first layer of the ids list
    root.findLayer(ids[0])

And groups can also be searched using their names:

.. testcode:: legend

    root.findGroup('Group Name')


:class:`QgsLayerTreeGroup <qgis.core.QgsLayerTreeGroup>` has many other useful
methods that can be used to obtain more information about the TOC:

.. testcode:: legend

    # list of all the checked layers in the TOC
    checked_layers = root.checkedLayers()
    print(checked_layers)

.. testoutput:: legend

    [<QgsVectorLayer: 'countries' (ogr)>]

Now let’s add some layers to the project’s layer tree. There are two ways of doing
that:

#. **Explicit addition** using the :meth:`addLayer() <qgis.core.QgsLayerTreeGroup.addLayer>`
   or :meth:`insertLayer() <qgis.core.QgsLayerTreeGroup.insertLayer>`
   functions:

   .. testcode:: legend

      # create a temporary layer
      layer1 = QgsVectorLayer("path_to_layer", "Layer 1", "memory")
      # add the layer to the legend, last position
      root.addLayer(layer1)
      # add the layer at given position
      root.insertLayer(5, layer1)

#. **Implicit addition**: since the project's layer tree is connected to the
   layer registry it is enough to add a layer to the map layer registry:

   .. testcode:: legend

       QgsProject.instance().addMapLayer(layer1)


You can switch between :class:`QgsVectorLayer <qgis.core.QgsVectorLayer>` and
:class:`QgsLayerTreeLayer <qgis.core.QgsLayerTreeLayer>` easily:


.. testcode:: legend

    node_layer = root.findLayer(country_layer.id())
    print("Layer node:", node_layer)
    print("Map layer:", node_layer.layer())

.. testoutput:: legend

    Layer node: <QgsLayerTreeLayer: countries>
    Map layer: <QgsVectorLayer: 'countries' (ogr)>


Groups can be added with the :meth:`addGroup() <qgis.core.QgsLayerTreeGroup.addGroup>`
method. In the example below, the former will add a group to the end of the TOC
while for the latter you can add another group within an existing one:

.. testcode:: legend

    node_group1 = root.addGroup('Simple Group')
    # add a sub-group to Simple Group
    node_subgroup1 = node_group1.addGroup("I'm a sub group")


To moving nodes and groups there are many useful methods.

Moving an existing node is done in three steps:

#. cloning the existing node
#. moving the cloned node to the desired position
#. deleting the original node

.. testcode:: legend

    # clone the group
    cloned_group1 = node_group1.clone()
    # move the node (along with sub-groups and layers) to the top
    root.insertChildNode(0, cloned_group1)
    # remove the original node
    root.removeChildNode(node_group1)

It is a little bit more *complicated* to move a layer around in the legend:

.. testcode:: legend

    # get a QgsVectorLayer
    vl = QgsProject.instance().mapLayersByName("countries")[0]
    # create a QgsLayerTreeLayer object from vl by its id
    myvl = root.findLayer(vl.id())
    # clone the myvl QgsLayerTreeLayer object
    myvlclone = myvl.clone()
    # get the parent. If None (layer is not in group) returns ''
    parent = myvl.parent()
    # move the cloned layer to the top (0)
    parent.insertChildNode(0, myvlclone)
    # remove the original myvl
    root.removeChildNode(myvl)

or moving it to an existing group:

.. testcode:: legend

    # get a QgsVectorLayer
    vl = QgsProject.instance().mapLayersByName("countries")[0]
    # create a QgsLayerTreeLayer object from vl by its id
    myvl = root.findLayer(vl.id())
    # clone the myvl QgsLayerTreeLayer object
    myvlclone = myvl.clone()
    # create a new group
    group1 = root.addGroup("Group1")
    # get the parent. If None (layer is not in group) returns ''
    parent = myvl.parent()
    # move the cloned layer to the top (0)
    group1.insertChildNode(0, myvlclone)
    # remove the QgsLayerTreeLayer from its parent
    parent.removeChildNode(myvl)


Some other methods that can be used to modify the groups and layers:

.. testcode:: legend

    node_group1 = root.findGroup("Group1")
    # change the name of the group
    node_group1.setName("Group X")
    node_layer2 = root.findLayer(country_layer.id())
    # change the name of the layer
    node_layer2.setName("Layer X")
    # change the visibility of a layer
    node_group1.setItemVisibilityChecked(True)
    node_layer2.setItemVisibilityChecked(False)
    # expand/collapse the group view
    node_group1.setExpanded(True)
    node_group1.setExpanded(False)