これは MIERUNE Advent Calendar 2024 の8日目の記事です。
昨日は @northprint さんによる DuckDB-Wasmで簡単な地理空間情報分析アプリを作る でした。
はじめに
QGISの機能のうち、プロセシングツールに含まれているものは、比較的簡単にpyqgisのコードを確認することができますよね。一方で、標準機能(ウィンドウ上部のメニューから起動するやつ)については、Python APIが用意されていないものもあります。(私が見つけられなかっただけ、という可能性もありますが)
本記事では、そんなQGISの標準機能をpyqgisで無理やり呼び出す方法について解説します。
予想される検索ワード
以下、この記事を求めているであろう方が入力しそうな検索ワードです。
QGIS, pyqgis, 標準機能
実行環境
- QGIS 3.34
実行方法
一回やってみる
兎にも角にも、一回動かしてみて、標準機能が本当に呼び出せるのかやってみましょう。
QGISのジオリファレンサ機能(下図)を呼び出すためのサンプルコードをご用意しました。
actions = iface.mainWindow().menuBar().actions()
for act in actions:
if act.menu().objectName() == "mLayerMenu":
for a in act.menu().actions():
if a.objectName() == "mActionShowGeoreferencer":
a.trigger()
break
break
サンプルコードをPythonコンソールで実行すると、ジオリファレンサが立ち上がることを確認できますね。
(ここが上手くいかない場合、QGISのverが3.34になっているか確認してみてください)
任意の機能を呼び出す方法
さて、ジオリファレンサ機能だけ呼び出せても仕方ないのでここからは任意の機能を呼び出す方法について解説していきます。
ご存知の方も多いと思いますが、QGISのGUIはQtフレームワークを用いて構築されています。そのため、上段のメニュー(メニューバー)についても、Qtフレームワークの仕組みを利用して操作(項目の追加、削除、実行など)することが可能です。
可能であれば、各機能に対して具体的なコードをサクッと示したいところですが、メニューバーには多くの機能が登録されているため、ここでそれぞれの呼び出し方を示すのは現実的ではありません。
そのため、ここからはハンズオン形式で手を動かしながら、任意の機能を呼び出す方法について解説していきたいと思います。
1. pyqgisでメニューの内容を取得してみる
まずは、以下のコードを実行してみましょう。
# メニューバーの全アクションを取得
actions = iface.mainWindow().menuBar().actions()
for act in actions:
# 各メニューのタイトルを表示
print(act.menu().title())
プロジェクト(&J)
編集(&E)
ビュー(&V)
レイヤ(&L)
設定(&S)
プラグイン(&P)
ベクタ(&O)
ラスタ(&R)
Web(&W)
メッシュ(&M)
プロセシング(&C)
ヘルプ(&H)
実行結果を見てみると、メニューバーの項目が取得できていることがわかりますね。
2. メソッドの正体を理解する
先ほどのコードで、当然のように使っているact.menu().title()
って何? と疑問に思った方もいらっしゃるかもしれません。その謎に迫るために、以下を実行してみましょう。
# メニューバーの全アクションを取得
actions = iface.mainWindow().menuBar().actions()
for act in actions:
# actのデータ型を表示
print(type(act))
# 各メニューのタイトルを表示
print(act.menu().title())
break
<class 'PyQt5.QtWidgets.QAction'>
プロジェクト(&J)
actの正体は、'PyQt5.QtWidgets.QAction'だということがわかりました。ということは、menu()
はQActionのメソッドであると推測できますね。
ということで、QActionのドキュメントを覗いてみましょう。
確かにQActionには、menu()
というFunctionが用意されていることが分かりますね。(上図の下から2行目に載っています)
つまり、サンプルコード上で実行していたメソッドは、Qt側で用意しているメソッドだったということが分かります。
3. 他の機能を呼び出してみる
上記で確認した通り、QGISのメニューバーはQtを利用して構築されているため、Qt側で用意している操作はpyqgisから実行することができます。
ということで、任意の機能を(Qtを利用して無理やり)起動させてみましょう。
今度は例として、オプション(設定→オプション)を起動させます。
まずは「設定」に含まれるアクション一覧を取得してみます。
# メニューバーの全アクションを取得
actions = iface.mainWindow().menuBar().actions()
for act in actions:
if act.menu().title() == "設定(&S)": # 設定タブかどうか判定
for a in act.menu().actions(): # 設定タブの全アクションでループ処理
print(a.text()) # 各アクションのテキストを表示
ユーザープロファイル(&U)
スタイルマネージャ...
カスタム投影法...
キーボードショートカット...
インタフェースのカスタマイズ...
オプション(&O)...
ちゃんと設定タブの各アクションを取得できていますね。あとは、アクションの名前からオプションを判別して、無理やり叩けば、起動できそうです。
ということで叩いてみます。
# メニューバーの全アクションを取得
actions = iface.mainWindow().menuBar().actions()
for act in actions:
if act.menu().title() == "設定(&S)":
for a in act.menu().actions():
if a.text() == "オプション(&O)...": # オプションかどうか判定
a.trigger() # アクションを走らせる
break
こんな感じで、各メニューの表記名を基にif文で判定しつつ、該当するメニューをtrigger()で叩いてやれば、無理やり処理を走らせることができます。
ちょっと応用的な話
(自身の環境で叩ければOKという方は、本節を読む必要はありません)
さて、お気づきになった方もいるかもしれませんね。
actions = iface.mainWindow().menuBar().actions()
for act in actions:
if act.menu().objectName() == "mLayerMenu":
for a in act.menu().actions():
if a.objectName() == "mActionShowGeoreferencer":
a.trigger()
break
break
# メニューバーの全アクションを取得
actions = iface.mainWindow().menuBar().actions()
for act in actions:
if act.menu().title() == "設定(&S)":
for a in act.menu().actions():
if a.text() == "オプション(&O)...": # オプションかどうか判定
a.trigger() # アクションを走らせる
break
「おい、サンプルコードは、title()
とかtext()
を使ってないじゃん! なんでサンプルコードと違うメソッド使ってんのさ!」
はい、その通りです。ハンズオンでは、実際にQGISのメニューが取得できていることを体感してもらうために、ワザとtitle()
やtext()
で判定させています。title()
やtext()
は、GUI上で表示されているテキストが取得できるので、実際にどのメニューを処理したいのか、分かりやすいですよね。
ですが、QGISを使うのは日本人だけだったでしょうか?違いますよね。
title()
やtext()
の内容はQGISの言語設定によって、揺れがあります。そのため、言語設定によらずに確実に起動させるためには、別の方法で判定させる必要があります。
サンプルコードで示したobjectName()
であれば、オブジェクト自体の名前(言語設定によらず不変)を取得することができるので、そういった多言語での利用想定(プラグインとして公開する等)に適した手法であると言えます。
おわりに
「プラグイン上の処理で標準機能を呼び出したい」というシチュエーションに陥ることは極めて稀だと思いますが、必要になった際は、今回のやり方を参考にして頂ければ幸いです。
おまけで、Qtの機能でできることは何でもできることを示すために、以下のコードを用意してみましたので、是非実行してみてくださいね!(素敵なメニューが追加されますよ!)
from qgis.PyQt.QtWidgets import QAction, QMenu
from qgis.utils import iface
import webbrowser
# "QGIS LAB"メニューを作成
qgis_lab_menu = QMenu("QGIS LAB", iface.mainWindow().menuBar())
iface.mainWindow().menuBar().addMenu(qgis_lab_menu)
# "QGIS LABにアクセス"アクションを作成
qgis_lab_action = QAction("QGIS LABにアクセス", iface.mainWindow())
help_action = QAction("ヘルプ", iface.mainWindow())
# アクションの動作を定義
def open_qgis_lab():
webbrowser.open("https://qgis.mierune.co.jp/")
def contact():
webbrowser.open("https://www.mierune.co.jp/contactus")
qgis_lab_action.triggered.connect(open_qgis_lab)
help_action.triggered.connect(contact)
# アクションをQGIS LABメニューに追加
qgis_lab_menu.addAction(qgis_lab_action)
qgis_lab_menu.addAction(help_action)
print("QGIS LAB menu added successfully!")
明日は@groovyjovyさんによる記事です!お楽しみに!