あさひコラムColumn
- 朝日航洋株式会社 TOPページ
- 知る・楽しむ
- あさひコラム
- QGISプラグインをつくってみよう
QGIS
QGISプラグインをつくってみよう
QGISは2024年10月現在、長期リリース版としてバージョン3.34、最新版としてバージョン3.38がリリースされており、非常に豊富な機能が搭載されています。
GISを使ってやりたいことの大半はQGISの標準機能を上手く組み合わせて実現できると思いますが、基本的には手動で画面操作をして目的の解析やデータ作成を行うことになるため、頻繁に行う操作の場合は少し煩わしさもあります。
QGISではそのような処理の自動化や自身で独自の処理を作るための手段として、プロセッシングツールとプラグインという2種類の拡張機能が用意されています。
プロセッシングツールについては以前の記事で取り上げているため、今回はプラグインの作り方についてご紹介したいと思います。
作りたいプラグインを決める
QGISはオープンソースのプロジェクトですので、世界中の方が様々なプラグインを開発されています。
日本国内に目を向けても、"QGIS プラグイン"などのキーワードでブラウザ検索してみると、プラグインの紹介や作り方に関する多くの記事を見つけることができます。
とはいえ、必ずしも自分のやりたいことにマッチするプラグインがあるわけではありません。
そのため、今回は一例としてラスタタイル・ベクタタイルの背景地図を管理・追加するプラグインを作ってみたいと思います。
QGISでも標準機能のブラウザパネルなどからタイルデータの管理・追加は可能ですが、利用するプロファイルが変わると設定内容が反映されないため、プラグインにすることでどの環境でQGISを利用しても使いたい背景地図をすぐに追加できる状態を目指します。
なお、QGISプラグインの作成には多かれ少なかれPythonのプログラムスキルが必要です。
本記事ではPythonの文法などに関する説明は行いませんので、興味のある方はご自身で調べてみてください。
前準備
筆者のプラグイン作成環境は以下になります。
・Windows 11 Pro (23H2)
・QGIS 3.34.11-Prizren
・Python 3.12.6(QGIS同梱)
Macユーザの方はQGISのインタフェースやコマンド入力などの操作が異なるため、適宜読み替えてください。
さて、プラグインを開発するためにはコードエディタなどのソフトウェアが必要になります。
エディタは色々と好みがありますが、今回はVisual Studio Code(VS Code)を使います。
VS Codeはこちらからダウンロードすることが可能です。
インストーラのダウンロードが完了したらexeを実行してVS Codeをインストールしてください。
VS Codeをエディタとして利用する理由として、プラグイン開発時のデバッグ設定の簡易さが挙げられます(個人的に)。
以下のリンクにVS Codeでプラグインをデバッグする手順が書いてありますので、設定してみてください。
chiakikunのブログ QGIS3プラグインをVisualStudioでデバッグするための設定
さらに、プラグインを作る上でインストールしておくと非常に便利なのが、"Plugin Builder 3"プラグインと"Plugin Reloader"プラグインです。
以下に各プラグインと主な役割を示します。
プラグイン名称 | 主な役割 |
---|---|
Plugin Builder 3 | 必要な事項を入力することでプラグインのベースとなるテンプレートを作成する。 |
Plugin Reloader | 選択したプラグインを即時リロードする。QGISのクライアントを開き直さなくて良いので便利。 |
Plugin Builder 3・Plugin Reloaderプラグインは次の手順でインストールを行います。
プラグインメニューから「プラグインの管理とインストール...」を選択します。
全プラグインタブがデフォルトで指定されているので、右側の検索用テキストボックスに"Plugin Builder 3"と入力し、フィルタされた結果を選択して「インストール」ボタンをクリックします。
2つのプラグイン全てをインストールできたら、インストール済タブに切り替えて正常に読み込めているかチェックします。
プラグインの読み込みに失敗している場合は、プラグイン名称が赤文字で表示されます。
また、それぞれのプラグインにチェックが入っているかもあわせて確認してください。
これで前準備は完了です。
プラグインのベースを作成する
続いて、先ほどインストールしたPlugin Builder 3を使ってプラグインのベースを作っていきます。
プラグインメニューからPlugin Builder>「Plugin Builder」をクリックします。
Plugin Builder 3のダイアログが立ち上がります。
以下の各項目を入力して「Next」ボタンをクリックします。
項目名 | 説明 |
---|---|
Class name | プラグインのクラス名を入力します。パスカルケース(先頭を含め各単語の頭文字を大文字にする)で記述することが推奨されています。 |
Plugin name | プラグインの名前を入力します。プラグイン管理画面等で表示され、日本語も利用可能です。 |
Description | プラグインの説明文を入力します。ここで入力した内容がプラグイン管理画面に表示されます(Plugin nameの一つ下)。 |
Module name | プラグインのモジュール名を入力します。スネークケース(全て小文字で単語同士をアンダースコアでつなぐ)で記述することが推奨されています。 |
Version number | プラグインのバージョン番号を入力します。 |
Minimum QGIS version | プラグインが動作するQGISの最低バージョンを入力します。QGIS3.x系のプラグインなので3.0以降で記述しましょう。 |
Author/Company | プラグインの作者名または会社名を入力する欄です。 |
Email address | プラグインの作者または会社のメールアドレスを入力する欄です。 |
続いて、プラグインの詳細な説明を入力します。
次の画面では、プラグインの画面構成のテンプレートや、どのメニューにプラグインを追加するかといった設定を行います。
今回は「Tool button with dialog」のテンプレートを採用するため、他のテンプレートの詳細説明は割愛します。
項目名 | 説明 |
---|---|
Template | 「Tool button with dialog」: 空のダイアログにOK・キャンセルボタンが付いたテンプレート 「Tool button with dock widget」: レイヤやブラウザパネルなどのように、QGIS画面の上下左右いずれかにドックとして固定表示できるテンプレート 「Processing Provider」: のいずれかから選択します。 |
Text for the menu item | プラグインメニューの表示名を入力します。 |
Menu | どのウィンドウメニューにプラグインを追加するか選択します。一般的には「Plugins」を選択すればOKです。 |
ここでは多言語対応やヘルプ、ユニットテストなどのテンプレートから作成したいものにチェックを入れて、「Next」ボタンをクリックします。
特に必要がなければ選択しなくても構いません。
この画面についても、個人で使う場合やテスト目的であれば入力する必要はありません。
ただ、プラグインを外部に公開する場合には、第三者が利用して不具合や要望があった場合の窓口として、Bug trackerやRepository、Home pageの情報を入力することが望ましいです。
また、「Flag the plugin as experimental」にチェックを入れると、プラグイン管理画面で”実験的なプラグイン”という扱いとなり、プラグイン設定で「実験的なプラグインも表示」にチェックを入れないとリストに表示されないようになります。
入力したら「Next」ボタンをクリックします。
最後に、プラグインのプログラム一式の出力先を指定します。
一般的にプラグインは以下の場所に格納され、別の場所に保存してしまうとプラグインが正しく動作しなくなってしまうため、特に理由がない限りは変更しないことをお勧めします。
C:\Users\<ユーザ名>\AppData\Roaming\QGIS\QGIS3\profiles\<プロファイル名>\python\plugins
<ユーザ名>はWindowsの自身のログインユーザ、<プロファイル名>はQGISで起動しているプロファイルです。
QGISはユーザごとの設定や環境を管理するためにプロファイルという仕組みを有しており、自身で別のプロファイルを作成していない限りは"default"プロファイルが常に使用されます。
そのため、複数のプロファイルを作成していない場合はdefaultとしてください。
指定し終わったら「Generate」ボタンをクリックします。
以下の画面が表示されたらプラグインのテンプレートは無事作成成功です。
「OK」ボタンをクリックしてダイアログを閉じてください。
※筆者環境では新しいプロファイルを作ってプラグイン作成をしています
なお、一度QGISを開き直す必要がありますが、Plugin Builder 3で作成したプラグインのテンプレートは管理画面から以下のように見えます。
一度この状態でプラグインの実物を確認しておきましょう。
現在「背景地図読み込みプラグイン」はチェックが外れている状態のため、チェックを付けて管理画面のダイアログを閉じてQGISも開き直してください。
QGISを再度立ち上げたら以下のようにプラグインメニューに「背景地図読み込みプラグイン」が追加されているはずですので、これをクリックします。
以下のようにOK・キャンセルボタンだけのダイアログが表示されるはずです。
以降の手順でプラグインの画面と処理を作成していきます。
プラグインフォルダの構成
Plugin Builder 3によって作成されたプラグインテンプレートのフォルダは以下のファイルで構成されています。
ファイル名 | 説明 |
---|---|
_init_.py | QGISプラグインのエントリポイントです。プラグイン本体のモジュールインポートや、プラグインの初期化処理が記載されています。このファイルを通じてQGISがプラグインを認識し、起動します。 |
basemap_loader_plugin.py | プラグインのメインロジックが記述されたモジュールです。 Plugin Builder 3で設定したクラス名がここで使用され、プラグインの初期化処理やQGISと連携した動作が記述されています。 __init__.pyで本モジュールが呼び出されます。 |
basemap_loader_plugin_dialog.py | プラグインのGUI(ダイアログ)に関するロジックを初期化するモジュールです。 UI上のイベント処理やユーザの操作に応じたロジックを記述します。 |
basemap_loader_plugin_dialog_base.ui | プラグインのGUIデザインの定義ファイルです。 ダイアログ内のボタンやテキストボックスなどのオブジェクトの配置に関する情報が定義されています。 このファイルはQt Designer経由で読み込み、必要な編集を行います。 |
icon.png | プラグインのアイコン画像ファイルです。 本記事ではアイコンの変更は行いませんが、カスタマイズする場合には画像ファイルの差し替えやresources.pyの更新が必要になります。 (参考)GIS実習オープン教材 プラグイン開発1 resources.pyの作成(https://gis-oer.github.io/gitbook/book/materials/python/10/10.html#resouces.py%E3%81%AE%E4%BD%9C%E6%88%90) |
metadata.txt | プラグインのメタデータが記載されています。 Plugin Builder 3で入力した各内容が反映されており、このファイルをもとにプラグイン管理画面の情報が表示されるようになります。 このファイルはプラグイン管理画面に表示される情報の元となり、バージョンアップの際はこのファイルの更新が必要です。 |
README.html / README.txt | プラグインのReadmeファイルです。 インストール方法や使い方などの情報を記述します。 |
resources.py / resources.qrc | resources.qrcはプラグインで使用するアイコンや画像ファイルなどのリソースを定義したファイルです。pyrcc5コマンドを使用して、この.qrcファイルをコンパイルすることで、Python形式のresources.pyが生成されます。プラグインに新しいリソースを追加する場合、このコンパイル手順が必要です。 |
これらのうち、今回直接編集をするのはbasemap_loader_plugin_dialog.py と basemap_loader_plugin_dialog_base.ui の2ファイルです。
Plugin Reloaderの使い方
Plugin Reloaderは、QGISを開いたまま任意のプラグインをリロードできる便利なプラグインです。
プラグインの開発中はコードの変更部分の動作を実際にQGIS上で確認する機会が非常に多いため、即時リロード(初期化)できるこのプラグインは開発には必須級です。
使い方は非常に簡単で、プラグインメニューのP プラグイン リローダ(バージョンや環境によって表示が異なるかもしれません)>「Reload a plugin...」をクリックします。
※Plugin Reloader バージョン0.14では一つ下の「設定」からはリロード対象のプラグインを指定することができないため注意してください。
ダイアログが開いたら、リロードするプラグインをプルダウンから選択して「OK」ボタンをクリックします。
プラグインがリロードされた旨のメッセージが表示されたら正常にリロードされています。
ちなみに、バージョン0.14では一度リロード対象のプラグインを設定すると、次回からはCtrl+F5キーでリロードできるようです。非常に便利ですね。
プラグインの画面を作成する
プラグインのテンプレートが作成できたら、次に画面周りを作成していきます。
QGISはQtというC++言語のフレームワークで画面が作成されており、Qtのテンプレートファイルを編集するためにQGISをインストールした際に同梱されているQt Designerを利用します。
WindowsのスタートメニューからQGIS3.34.11のフォルダを探して、配下の「Qt Designer with QGIS 3.34.11 custom widgets」をクリックします。
Qt Designerを開くと新規フォーム作成のダイアログが表示されますが、今回はPlugin Builder 3で作成されたGUIのデザインファイルがありますので、「開く...」ボタンをクリックしてこれを選択します。
defaultプロファイルの場合は以下がUIファイルのパスになります。
C:\Users\<ユーザ名>\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins\basemap_loader_plugin\basemap_loader_plugin_dialog_base.ui
ファイルを開くと、プラグインのダイアログがエディタ上に表示されます。
以下の項目をダイアログに追加します。
背景地図リスト(タブ1)
・設定したタイル設定一覧を表示するTableView
・地図上に背景地図を追加するpushButton
新規タイル設定(タブ2)
・背景地図のタイトル
・タイルURL
・最小ズームレベル
・最大ズームレベル
・リファラ
・タイルのスタイルURL(ベクタタイルのみ)
・設定を追加するpushButton
このプラグインのGUIは以下の点に留意して作成しています。
・各パーツを垂直に並べる(タブ要素と閉じるボタンの配置、タブ内の各パーツの配置)
◦こうすることでダイアログのサイズや画面解像度が変わっても整列したオブジェクト配置にすることができます
・各パーツ内の要素は項目によって垂直配置と水平配置を使い分ける
・各パーツの余分なスペースはスペーサーを挿入して一方向に寄せる
QtDesignerを使ったUIデザインについてはウェブ上で様々な記事がありますので興味があれば調べてみてください。
プラグインのロジックを実装する
GUIの作成が終わりましたので、次はプラグイン内部の処理を実装していきます。
今回は必要な処理を全てbasemap_loader_plugin_dialog.pyに記述しますが、実際にプラグインを作成する場合には処理内容に応じて適宜モジュールを作成し、ロジックを分離することが望ましいです。
サンプルプラグインの機能として以下を実装します。
1. 初期表示用のタイル一覧を記述したJSONファイルを作成する
2. 背景地図リストのタブにJSONのタイル一覧を出力する
3. 2のタイル一覧の任意の行を選択し、「マップキャンバスに追加」ボタンをクリックしたらタイルレイヤとして地図上に追加する
4. 2のタイル一覧の任意の行を選択し、「選択行の削除」ボタンをクリックしたら一覧とJSONから該当タイルを削除する
5. 新規タイル設定タブの各項目が入力された状態で「設定の追加」ボタンをクリックしたら該当タイルをJSONと2の一覧に追加する
各機能に関して、ポイントを絞ってご説明します。
1. 初期表示用のタイル一覧を記述したJSONファイルを作成する
プラグインフォルダの直下にbasemap.jsonとして以下のJSONファイルを作成します。
ファイルの作成はBasemapLoaderPluginDialogクラスのコンストラクタに記述します。
class BasemapLoaderPluginDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, parent=None): #<<中略>> # デフォルトで表示するための背景地図リストのJSONファイルを作成 if not (os.path.isfile(JSON_FILE)): try: with open(JSON_FILE, 'w', encoding='utf-8') as f: json.dump(self.create_default_basemap(), f, ensure_ascii=False, indent=4) except OSError as e: self.show_message("エラー", CREATE_ERR_MSG, Qgis.Warning) QgsMessageLog.logMessage(traceback.format_exc(), "プラグイン", QgsMessageLog.WARNING) raise
JSON_FILE定数にはbasemap.jsonまでのフルパスを格納します。
プラグインフォルダ直下に作成するので、以下のようにQGISの現在開いているプロファイルまでのパスを取得し、それに加えてプラグインまでのパスを結合します。
# JSONファイルの保存先の定義 JSON_FILE = os.path.join(QgsApplication.qgisSettingsDirPath(), "python", "plugins", "basemap_loader_plugin", "basemap.json")
また、show_message関数は以下のように記述し、エラーが発生した時などにQGISの画面上部のメッセージバーにエラー内容を出力します。
def show_message(self, message_title, message, level): self.iface.messageBar().pushMessage( message_title, message, level=level, duration=5 )
ifaceはQGISのUIや基本的なコンポーネントにアクセスするためのオブジェクトです。マップキャンバスへのアクセスやレイヤの追加、取得など頻繁に使用されるものが多いです。今回はiface.messageBar()でメッセージバーにエラーを出力する操作を行っています。
デフォルトで読み込むタイルは以下のように地理院地図のラスタ・ベクタタイルを中心としておきます。
{ "地理院地図(淡色地図)": { "url": "https://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png", "minzl": 0, "maxzl": 18, "referer": "", "styleurl": "", "type": "raster" }, "地理院地図(標準地図)": { "url": "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png", "minzl": 0, "maxzl": 18, "referer": "", "styleurl": "", "type": "raster" }, "地理院地図(白地図)": { "url": "https://cyberjapandata.gsi.go.jp/xyz/blank/{z}/{x}/{y}.png", "minzl": 5, "maxzl": 14, "referer": "", "styleurl": "", "type": "raster" }, "地理院地図(写真)": { "url": "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg", "minzl": 14, "maxzl": 18, "referer": "", "styleurl": "", "type": "raster" }, "OpenStreetMap": { "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "minzl": 0, "maxzl": 18, "referer": "", "styleurl": "", "type": "raster" }, "地理院地図(ベクトルタイル)": { "url": "https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf", "minzl": 4, "maxzl": 17, "referer": "", "styleurl": "https://maps.gsi.go.jp/vector/data/pale.json", "type": "vector" } }
2. 背景地図リストのタブにJSONのタイル一覧を出力する
1で作成したJSONをtableWidgetコンポーネントのアイテムとして追加する処理をコンストラクタに記述します。
def add_table_item(self, tableWidget, basemap_data): """テーブルウィジェットに背景地図データのアイテムを追加する Args: tableWidget (QTableWidget): 背景地図リストを表示するテーブルウィジェットオブジェクト basemap_data (dict): 背景地図JSONの辞書オブジェクト """ tableWidget.setRowCount(0) row = 0 for key, value in basemap_data.items(): tableWidget.insertRow(row) tableWidget.setItem(row, 0, QTableWidgetItem(key)) tableWidget.setItem(row, 1, QTableWidgetItem(value['url'])) tableWidget.setItem(row, 2, QTableWidgetItem(str(value['minzl']))) tableWidget.setItem(row, 3, QTableWidgetItem(str(value['maxzl']))) tableWidget.setItem(row, 4, QTableWidgetItem(value['referer'])) tableWidget.setItem(row, 5, QTableWidgetItem(value['styleurl'])) tableWidget.setItem(row, 6, QTableWidgetItem(value['type'])) row += 1
setItemで格納するセルの行・列を指定し、QTableWidgetItemとしてタイルのタイトルやURL、ズームレベルなどを格納していきます。
格納した結果は以下のような画面表示になります。
3. 2のタイル一覧の任意の行を選択し、「マップキャンバスに追加」ボタンをクリックしたらタイルレイヤとして地図上に追加する
QTableWidgetは初期状態だとセル単位で選択されるため、コンストラクタに行選択を強制するよう記述します。
# テーブルウィジェットを行選択にする self.tableWidget.setSelectionBehavior(QTableWidget.SelectRows)
また、ダブルクリックでセルが編集モードにならないようにセルの編集を禁止する処理もコンストラクタに加えます。
# セルの編集を禁止にする self.tableWidget.setEditTriggers(QTableWidget.NoEditTriggers)
「マップキャンバス」ボタンをクリックした時に所定の処理を実行するには、コンストラクタに以下の処理を追加します。
self.pushButton_addMap.clicked.connect(self.add_tile_layer)
connect()はPyQt5の関数で、シグナル(ボタンクリックのイベント clicked)とスロット(シグナルが発生した際に呼び出される関数 self.add_tile_layer)という仕組みに基づいています。
シグナルは他にも、テキストの値が変化した時(textChanged)やコンボボックスの選択が変化した時(currentIndexChanged)など様々あります。
スロットに定義する関数は以下になります。
def add_tile_layer(self): """選択されたタイルデータをマップキャンバスに追加する Raises: ValueError: 選択されたデータが不正な場合に発生する """ selected_row = self.tableWidget.currentRow() if selected_row >= 0: layer = None # 選択行の全ての列の値を取得する selected_data = self.get_row_values() # URIの定義 uri = f'styleUrl={selected_data[5]}&type=xyz&url={selected_data[1]}&zmax={selected_data[3]}&zmin={selected_data[2]}&http-header:referer={selected_data[4]}' # レイヤインスタンスの作成する if selected_data[6] == "raster": layer = QgsRasterLayer(uri, selected_data[0], 'wms') else: layer = QgsVectorTileLayer(uri, selected_data[0]) # マップキャンバスにタイルを追加する QgsProject.instance().addMapLayer(layer)
self.get_row_values()関数は、テーブルウィジェットの選択行の全ての列の値を取得して、リストに加えた状態で返却しています。
リストの末尾にはラスタタイル・ベクタタイルの種別としてrasterとvectorの2種類のいずれかが格納されており、それによってタイルレイヤの追加処理を分岐しています。
最後のaddMapLayer()関数で、作成したレイヤインスタンスを引数としてマップキャンバスにレイヤを追加します。
4. 2のタイル一覧の任意の行を選択し、「選択行の削除」ボタンをクリックしたら一覧とJSONから該当タイルを削除する
先ほどと同様に「選択行を削除」ボタンのイベントハンドラをコンストラクタに定義します。
self.pushButton_delete.clicked.connect(self.delete_tile_settings)
選択行削除のスロットに定義する関数は以下になります。
def delete_tile_settings(self): """選択行のタイル設定をJSONファイルから削除する Raises: ValueError: 選択データが不正な場合に発生する """ # 選択行のインデックスを取得 selected_row = self.tableWidget.currentRow() if selected_row >= 0: # 選択行の背景地図タイトルの値を取得 item = self.tableWidget.item(selected_row, 0) # 削除後のJSONをファイルに反映する basemap_data = self.load_json() basemap_data.pop(item.text()) self.save_json(basemap_data) self.tableWidget.removeRow(selected_row) self.show_message("情報", "選択したタイル設定を削除しました", Qgis.Info)
選択行のインデックスを取得するには、テーブルウィジェットインスタンスのcurrentRow()関数を利用します。
テーブルウィジェットのアイテム削除と合わせてJSONからも該当箇所を削除する必要があるため、JSONを読み込んだ後にキー指定でデータを削除しています。
テーブルウィジェットからアイテムを削除するにはremoveRow()関数を使います。
5. 新規タイル設定タブの各項目が入力された状態で「設定の追加」ボタンをクリックしたら該当タイルをJSONと2の一覧に追加する
最後に、新規タイル設定タブの処理を実装します。
「設定の追加」ボタンのイベントハンドラをコンストラクタに定義します。
self.pushButton_addSettings.clicked.connect(self.save_tile_settings)
スロットに定義する関数は以下になります。
def save_tile_settings(self): """入力されたタイル設定をJSONファイルに保存し、テーブルに追加する Raises: ValueError: 入力データが不正な場合に発生する """ if not self.validate_input(): return # テーブルの行数を取得する row_position = self.tableWidget.rowCount() # 新しい行の追加 self.tableWidget.insertRow(row_position) data = { "url": self.lineEdit_url.text(), "minzl": self.spinBox_minZl.text(), "maxzl": self.spinBox_maxZl.text(), "referer": self.lineEdit_referer.text(), "styleurl": self.lineEdit_styleUrl.text(), "type": self.comboBox_type.currentText() } # 入力された内容をJSONファイルに保存する basemap_data = self.load_json() basemap_data[self.lineEdit_title.text()] = data self.save_json(basemap_data) self.add_table_item(self.tableWidget, basemap_data) self.show_message("情報", "タイル設定を追加・保存しました", Qgis.Info)
テーブルウィジェットインスタンスのrowCount()関数で、現在のテーブルアイテムの行数を取得し、insertRow()関数で最終行の次に新しいタイル設定を追加します。
背景タイルリストはJSONをもとに生成するため、削除時と同様にJSONファイルへの書き込みも必要です。
以上で一通りの機能の実装が完了しました。
終わりに
前述した通り、QGISプラグインに関する情報はWebで検索すると沢山出てきます。
Web以外でもQGISやPyQt関連の書籍も出版されており、より体系的にプラグイン開発の学習をすることが可能です。
さらに、より最新の情報や技術的な細かい情報をキャッチしたいという方は、イベントに参加してみるのもオススメです。
OSGeo日本支部が毎年FOSS4Gというイベントを開催しており、QGISのみならずオープンソースの地理空間プロジェクトに関する様々な情報収集をすることができるほか、ジオ系の開発者と情報交換を行うことができます。
QGISはちょっとした工夫でとても使いやすいツールになります。
ぜひプラグイン作成にトライしてみてください!
参考資料
QGIS API Documentation
GIS初中級者のためのPython入門
PyQt5入門(1)
QGIS3.x系向けプラグイン作成手順や各種処理の実装方法について