PyQGISでタイルレイヤのURLを取得したい

# はじめに
この記事は、
**「タイルのURL取得なんて余裕だと思ってたのに、なぜかできない。。。」**
と謎にハマってしまった人（数日前の自分）に向けた記事です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1147458/1ca4e1c8-1d46-305c-cb5b-5c57e7a2d37f.png)

# 予想される検索ワード
以下、この記事を求めているであろう方が入力しそうな
（というか実際に私が必死で検索した）検索ワードです。

QGIS, PyQGIS, タイル, URL, 取得

# 実行環境
- Windows11
- QGIS 3.28

# 忙しい人向けの結論
QGISでタイルレイヤを選択している状態で、Pythonコンソールで以下を実行すれば取得できます。

```python:Pythonコンソール
# アクティブレイヤ
layer = iface.activeLayer()

# レイヤのソースを取得
src = layer.source()

# ソースを"&"で分けてリスト化
src_list = src.split("&")

# "url="で始まる要素を取得
url = [s for s in src_list if s.startswith('url=')][0]

# 先頭の"url="という文字は不要なので削除
url = url.lstrip("url=")

# URLデコード
url = url.replace("%7B", "{").replace("%7D", "}")

print(url)
```

# タイルのURLを取得するために
取得方法自体は上述の通りなので、
ここからは「なんで上記のコードで取得できるの？」について書いていきます。

## layer.source()とは
私が混乱した諸悪の根源です。
というか、そもそも[PyQGISのドキュメント](https://qgis.org/pyqgis/master/)に"layer.source()"って載ってないですよね？
「アンタは一体何なんだ！」って感じです。

とりあえず、素直にQGISに質問（ただのhelp）してみると以下のように返してくれます。

```
>>> help(qgis._core.QgsRasterLayer.source)

Help on built-in function source:

source(...)
    source(self) -> str
    Returns the source for the layer. This source may contain usernames, passwords
    and other sensitive information.
    
    .. seealso:: :py:func:`publicSource`
```

ふむふむ。なんだかレイヤのソース（URL）を返してくれそうですね。

## layer.source()を試してみる
さて、みんな大好き地理院タイルで確認してみましょう。
（きっとタイルのURLを返してくれるに違いない！）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1147458/786010fb-b309-98f7-8c95-33429be7f85d.png)

　
<font>$\huge{「...いや、欲しいのはそっちじゃないんや。」}$</font>
　

世の中、そう甘くはないですね。

ちなみに、私はここから **「きっと、素直にURLを返す関数があるに違いない！」** と
関数探しの旅に出（て帰ってこれなくなり）ました。

## layer.source()の戻り値
関数探しの旅に出ると帰って来れなくなる恐れがあるので
素直にlayer.source()の戻り値と向き合います。

**指定レイヤがWebマップタイルだった場合**の
layer.source()の戻り値は基本的に、
```python
key1=value1&key2=value2&key3=value3&...
```
という構造のようです。
（指定レイヤがWebマップタイルでない場合は、この限りでは無いです）

今回返ってきた値を分解してみると
```python
type=xyz # xyzタイル形式だよ
&
url=https://cyberjapandata.gsi.go.jp/xyz/pale/%7Bz%7D/%7Bx%7D/%7By%7D.png # urlはここだよ
&
zmax=18&zmin=0 # ズームレベル0～18だよ
&
http-header:referer= # HTTPリファラは無いよ
```
ということを示しています。

## URLの取得
layer.source()の戻り値を見てみたところ、
"url="に割り当てられている値がそれっぽいので、取得してみましょう。
```python:Pythonコンソール
# アクティブレイヤ
layer = iface.activeLayer()

# レイヤのソースを取得
src = layer.source()

# ソースを"&"で分けてリスト化
src_list = src.split("&")

# "url="で始まる要素を取得
url = [s for s in src_list if s.startswith('url=')][0]

# 先頭の"url="という文字は不要なので削除
url = url.lstrip("url=")

print(url)
# => https://cyberjapandata.gsi.go.jp/xyz/pale/%7Bz%7D/%7Bx%7D/%7By%7D.png
```
それっぽい値が取得できましたね。
あとは"%7B"とか"%7D"をどうにかすれば良さそうです。

## URLエンコード
"%7B"とか"%7D"は
URLエンコードによって **"{" とか "}" が変換された値**です。

> URLエンコード、とは、URLとして使用できない文字を扱う際に、利用可能な文字に変換（エンコード）する事を言います。パーセントエンコーディング、とも言います。
>> 引用元：https://www.en-pc.jp/tech/urlencode.php

そのため、"%7B"とか"%7D"をデコード（元に戻す）すれば良いです。
```python:Pythonコンソール
# URLデコード
url = url.replace("%7B", "{").replace("%7D", "}")

print(url)
# => https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png
```
上手く取得することができました。めでたしめでたし。

# おわりに
「タイルのURL取得なんてQGISのプロパティでサクッと見られるんだから余裕でしょ」
と思っていたのですが、意外にも手間取ってしまいました。（主に関数探しのせい）

皆様も関数探しの旅は、ほどほどに。。。

　
...あ、urlを一発で取得できる関数を知っている方がいれば、教えてください。