PyQGISでレイヤパネルの操作を試した時のメモ

## はじめに
QGISでプラグインを作成していると、レイヤパネルを操作したい場面が多々あります。
この記事は、PyQGISでレイヤパネルを操作する方法について調べた時の覚書です。

## 前提
レイヤパネルには、以下のようにレイヤが追加されていることを前提とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/a3df1fc9-551d-1493-92e8-f2bac4805b4b.png)

- ルート
    - QgsLayerTreeオブジェクト
    - これが最上位のオブジェクト
- ルートの子
    - QgsLayerTreeLayer
    - QgsLayerTreeGroup
    
## ルートの子を取得する
```python
root = QgsProject.instance().layerTreeRoot()
print(root)
print(root.children())

# <qgis._core.QgsLayerTree object at 0x0000600001e61200>
# [<QgsLayerTreeGroup: 交通>, <QgsLayerTreeLayer: 海岸線>, <QgsLayerTreeLayer: 行政界>, <QgsLayerTreeLayer: el256>, <QgsLayerTreeLayer: lu>, <QgsLayerTreeGroup: 背景地図>]
```

## ルートの最初の子ノードにアクセス
```python
root = QgsProject.instance().layerTreeRoot()
child0 = root.children()[0]
print(child0)
print(type(child0))
print(isinstance(child0, QgsLayerTreeLayer))
print(child0.parent())
print(root.parent())

# <QgsLayerTreeGroup: 交通>
# <class 'qgis._core.QgsLayerTreeGroup'>
# False
# <qgis._core.QgsLayerTree object at 0x0000600001e61200>
# None
```

## グループとそのノードを取得する
```python
from qgis.core import QgsLayerTreeGroup, QgsLayerTreeLayer

def get_group_layers(group):
   print('- group: ' + group.name())
   for child in group.children():
      if isinstance(child, QgsLayerTreeGroup):
         # Recursive call to get nested groups
         get_group_layers(child)
      else:
         print('  - layer: ' + child.name())

root = QgsProject.instance().layerTreeRoot()
for child in root.children():
    if isinstance(child, QgsLayerTreeGroup):
      get_group_layers(child)
    elif isinstance(child, QgsLayerTreeLayer):
      print ('- layer: ' + child.name())

# - group: 交通
#  - layer: 空港
#   - layer: 道路
# - group: 鉄道
#   - layer: 線路
#   - layer: 駅
# - layer: 海岸線
# - layer: 行政界
# - layer: el256
# - layer: lu
# - group: 背景地図
#   - layer: Mapzen Global Terrain
#   - layer: OpenStreetMap

```

## ルートからツリーレイヤーを取得する
```python
ids = root.findLayerIds()
print (ids)
print (root.findLayers())
print (root.findLayer(ids[0]))

#['airp_jpn_19313c29_cd74_4b31_b8bf_b5a4743a813e', 'roadl_jpn_42fbebf7_2896_48c6_9b2c_0c6968b07c12', 'raill_jpn_a5c85270_8b05_4fcd_a3cd_11175d18f21f', 'N02_22_Station_ed27438c_f217_4b83_96e7_a1b398b838fd', 'coastl_jpn_2767651f_9d89_464b_9a5c_60ec25c2026f', 'bnda_1_1_33e9c638_1e23_4073_8f00_3d2bd4e6ee7e', 'el256_82f42cac_5844_4620_bf1d_0363006ff40d', 'lu_fd1b9e12_eb64_418b_879f_27516f33d80f', 'Mapzen_Global_Terrain_8174a9f5_d71c_47e0_95af_32d4e5c59b8f', 'OpenStreetMap_17b01ae4_e89a_46c1_bba9_52fcd4163918']
# [<QgsLayerTreeLayer: 空港>, <QgsLayerTreeLayer: 道路>, <QgsLayerTreeLayer: 線路>, <QgsLayerTreeLayer: 駅>, <QgsLayerTreeLayer: 海岸線>, <QgsLayerTreeLayer: 行政界>, <QgsLayerTreeLayer: el256>, <QgsLayerTreeLayer: lu>, <QgsLayerTreeLayer: Mapzen Global Terrain>, <QgsLayerTreeLayer: OpenStreetMap>]
# <QgsLayerTreeLayer: 空港>
```

## 親ノードを取得する
```python
layer = QgsProject.instance().mapLayersByName("線路")[0]
root = QgsProject.instance().layerTreeRoot()
my_layer = root.findLayer(layer.id())
parent = my_layer.parent()

print("myLayer" ,myLayer)
print("p1:", parent)
print("p2:", parent.parent())
print("p3:", parent.parent().parent())
print("p4:", parent.parent().parent().parent())

# myLayer <QgsLayerTreeLayer: 線路>
# p1: <QgsLayerTreeGroup: 鉄道>
# p2: <QgsLayerTreeGroup: 交通>
# p3: <qgis._core.QgsLayerTree object at 0x0000600001e61200>
# p4: None
```

## グループを取得する
```python
groups = QgsProject().instance().layerTreeRoot().findGroups()
group_names = list(map(lambda g: g.name(), groups))
traffic_group = root.findGroup("交通")
railroad_group = root.findGroup("鉄道")

print(groups)
print(group_names)
print(traffic_group)
print(railroad_group)

# [<QgsLayerTreeGroup: 交通>, <QgsLayerTreeGroup: 背景地図>]
# ['交通', '背景地図']
# <QgsLayerTreeGroup: 交通>
# <QgsLayerTreeGroup: 鉄道>
```

## レイヤIDからツリーレイヤを取得する
```python
layer = QgsProject.instance().mapLayersByName('線路')[0]
railroad_layer =  root.findLayer(layer.id())

# <QgsLayerTreeLayer: 線路>
```

## レイヤを追加する
```python
layer = QgsVectorLayer("Point?crs=EPSG:4326", "TEST", "memory")
# falseをつけるとレイヤパネルの最下部に追加される
QgsProject.instance().addMapLayer(layer, False)
node_layer = root.addLayer(layer)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/c810fe9b-ac07-004d-1f29-41cb89e93f74.png)

## レイヤを削除する
```python
layer = QgsProject.instance().mapLayersByName('TEST')[0]
QgsProject.instance().removeMapLayer(layer.id())
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/3513aa7a-90f2-1cc6-7ace-9d9181e0f323.png)

## 指定した位置にレイヤを追加する
```python
layer = QgsVectorLayer("Point?crs=EPSG:4326", "TEST", "memory")
layerTree = iface.layerTreeCanvasBridge().rootGroup()
QgsProject.instance().addMapLayer(layer, False)
# 上から３番目の位置にレイヤを追加
layerTree.insertChildNode(2, QgsLayerTreeLayer(layer))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/e7d72fe6-17b2-0269-0037-1b9610fb2f7f.png)

## 指定したレイヤの真下にレイヤを追加
```python
# el256の下にレイヤを追加する
ref_layer = QgsProject.instance().mapLayersByName("el256")[0]
insert_layer = QgsVectorLayer("Point", "TEST2", "memory")
root = QgsProject.instance().layerTreeRoot()
QgsProject.instance().addMapLayer(insert_layer, False)
QgsLayerTreeUtils.insertLayerBelow(root, ref_layer, insert_layer)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/e8dee3bf-46b8-f5b6-eb5b-85a8fd5d9d4d.png)


## レイヤをグループ内に移動
```python
# TESTレイヤを交通グループ内に移動する
layer = QgsProject.instance().mapLayersByName("TEST")[0]
root = QgsProject.instance().layerTreeRoot()

my_layer = root.findLayer(layer.id())
my_clone = my_layer.clone()
parent = my_layer.parent()

my_group = root.findGroup("交通")
my_group.insertChildNode(0, my_clone)

parent.removeChildNode(my_layer)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/a56a4dff-135b-5a2e-e2ea-7f35cab16eeb.png)


## グループを追加する
```python
root = QgsProject.instance().layerTreeRoot()
node_group = QgsLayerTreeGroup("Group")
root.addChildNode(node_group)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/ca9ee01f-05a2-438d-7a2a-4d2a27934234.png)

## グループを削除する
```python
root = QgsProject.instance().layerTreeRoot()
node_group = root.findGroup("Group")
root.removeChildNode(node_group)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/5f5a34dd-c384-ddf0-60dd-c3fec1ec04d2.png)


## レイヤとグループの表示・非表示を変更する
```python
root = QgsProject.instance().layerTreeRoot()
layer = QgsProject.instance().mapLayersByName('TEST')[0]
target_layer = root.findLayer(layer.id())
target_group = root.findGroup("交通")

# レイヤを非表示
target_layer.setItemVisibilityChecked(False)
# グループを非表示
target_group.setItemVisibilityChecked(False)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/14ceb9f5-1e59-8ff4-52ff-712fdc92f4f7.png)


## グループの展開・折り畳む
```python
my_group = QgsProject.instance().layerTreeRoot().findGroup("背景地図")
my_group.setExpanded(False)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/670401/bea5582d-ea12-5dd1-cc56-90ec276d24a0.png)


## 参考
- [21. Cheat sheet for PyQGIS - QGIS Documentation documentation](https://docs.qgis.org/3.10/ja/docs/pyqgis_developer_cookbook/cheat_sheet.html)
- [QGIS Layer Tree API (Part 1) - Lutra Consulting](https://www.lutraconsulting.co.uk/blog/2014/07/06/qgis-layer-tree-api-part-1/)
- [QGIS Layer Tree API (Part 2) - Lutra Consulting](https://www.lutraconsulting.co.uk/blog/2014/07/25/qgis-layer-tree-api-part-2/)
- [Difference between the .addMapLayer() vs .layerTreeRoot().insertChildNode() methods of QgsProject.instance()?](https://gis.stackexchange.com/questions/438557/difference-between-the-addmaplayer-vs-layertreeroot-insertchildnode-meth)