プラグイン開発の基礎を押さえたら、より実践的な機能を実装しましょう。
QGISプラグイン開発【実践編】
この記事では、実務で役立つプラグインの実装パターンを解説します。
処理ツールの実装
Processing Provider
QGISの処理ツールボックスに独自ツールを追加できます。
プロバイダークラス:
python
from qgis.core import QgsProcessingProvider
class MyProvider(QgsProcessingProvider):
def loadAlgorithms(self):
self.addAlgorithm(MyProcessingAlgorithm())
def id(self):
return 'myplugin'
def name(self):
return 'My Plugin'
def icon(self):
return QgsProcessingProvider.icon(self)
アルゴリズムクラス:
python
from qgis.core import (
QgsProcessingAlgorithm,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterNumber,
QgsProcessingParameterFeatureSink
)
class MyProcessingAlgorithm(QgsProcessingAlgorithm):
INPUT = 'INPUT'
DISTANCE = 'DISTANCE'
OUTPUT = 'OUTPUT'
def name(self):
return 'mybuffer'
def displayName(self):
return 'マイバッファ'
def group(self):
return 'ベクター処理'
def groupId(self):
return 'vector'
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterVectorLayer(
self.INPUT,
'入力レイヤ'
)
)
self.addParameter(
QgsProcessingParameterNumber(
self.DISTANCE,
'バッファ距離',
defaultValue=100
)
)
self.addParameter(
QgsProcessingParameterFeatureSink(
self.OUTPUT,
'出力レイヤ'
)
)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsVectorLayer(parameters, self.INPUT, context)
distance = self.parameterAsDouble(parameters, self.DISTANCE, context)
(sink, dest_id) = self.parameterAsSink(
parameters, self.OUTPUT, context,
source.fields(), source.wkbType(), source.crs()
)
total = source.featureCount()
for current, feature in enumerate(source.getFeatures()):
if feedback.isCanceled():
break
# バッファ処理
buffered = feature.geometry().buffer(distance, 5)
new_feature = QgsFeature()
new_feature.setGeometry(buffered)
new_feature.setAttributes(feature.attributes())
sink.addFeature(new_feature)
feedback.setProgress(int(current / total * 100))
return {self.OUTPUT: dest_id}
def createInstance(self):
return MyProcessingAlgorithm()
プログレスバーの実装
ダイアログ内のプログレスバー
python
from qgis.PyQt.QtWidgets import QProgressDialog
from qgis.PyQt.QtCore import Qt
def run_with_progress(self):
layer = self.iface.activeLayer()
total = layer.featureCount()
# プログレスダイアログを作成
progress = QProgressDialog("処理中...", "キャンセル", 0, total)
progress.setWindowModality(Qt.WindowModal)
progress.show()
for i, feature in enumerate(layer.getFeatures()):
if progress.wasCanceled():
break
# 処理
self.process_feature(feature)
progress.setValue(i + 1)
progress.close()
メッセージバーのプログレス
python
from qgis.core import Qgis
from qgis.PyQt.QtWidgets import QProgressBar
def run_with_messagebar_progress(self):
# プログレスバーを作成
progressBar = QProgressBar()
progressBar.setMaximum(100)
# メッセージバーに追加
messageBar = self.iface.messageBar().createMessage("処理中...")
messageBar.layout().addWidget(progressBar)
self.iface.messageBar().pushWidget(messageBar, Qgis.Info)
# 処理
for i in range(100):
# 処理を実行
progressBar.setValue(i + 1)
# 完了
self.iface.messageBar().clearWidgets()
self.iface.messageBar().pushMessage("完了", "処理が終了しました", Qgis.Success)
設定の保存と読み込み
QgsSettings
python
from qgis.core import QgsSettings
class MyPlugin:
def __init__(self, iface):
self.iface = iface
self.settings = QgsSettings()
def save_settings(self):
# 設定を保存
self.settings.setValue("myplugin/last_path", "/path/to/file")
self.settings.setValue("myplugin/buffer_distance", 100)
self.settings.setValue("myplugin/enabled", True)
def load_settings(self):
# 設定を読み込み(デフォルト値付き)
path = self.settings.value("myplugin/last_path", "")
distance = self.settings.value("myplugin/buffer_distance", 50, type=int)
enabled = self.settings.value("myplugin/enabled", False, type=bool)
return path, distance, enabled
プロジェクト固有の設定
python
from qgis.core import QgsProject
# プロジェクトに設定を保存
project = QgsProject.instance()
project.writeEntry("myplugin", "setting_name", "value")
# 読み込み
value, ok = project.readEntry("myplugin", "setting_name", "default")
外部ライブラリの利用
標準ライブラリ
python
import os
import json
import csv
from datetime import datetime
pandas(インストール済みの場合)
python
try:
import pandas as pd
HAS_PANDAS = True
except ImportError:
HAS_PANDAS = False
def process_with_pandas(self):
if not HAS_PANDAS:
QMessageBox.warning(None, "エラー", "pandasがインストールされていません")
return
# pandasを使った処理
df = pd.read_csv("data.csv")
requestsでAPI連携
python
import requests
def fetch_api_data(self, url):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
QgsMessageLog.logMessage(f"API error: {e}", "MyPlugin", Qgis.Warning)
return None
エラーハンドリング
基本的なエラー処理
python
from qgis.core import QgsMessageLog, Qgis
def run(self):
try:
self.do_processing()
except FileNotFoundError as e:
QMessageBox.critical(None, "エラー", f"ファイルが見つかりません: {e}")
except ValueError as e:
QMessageBox.warning(None, "警告", f"値が不正です: {e}")
except Exception as e:
QgsMessageLog.logMessage(f"予期しないエラー: {e}", "MyPlugin", Qgis.Critical)
QMessageBox.critical(None, "エラー", "予期しないエラーが発生しました。ログを確認してください。")
入力検証
python
def validate_input(self):
layer = self.dialog.layerComboBox.currentLayer()
if layer is None:
QMessageBox.warning(None, "警告", "レイヤを選択してください")
return False
distance = self.dialog.distanceSpinBox.value()
if distance <= 0:
QMessageBox.warning(None, "警告", "距離は正の数を入力してください")
return False
return True
マルチスレッド処理
QgsTask
長時間の処理はバックグラウンドで実行:
python
from qgis.core import QgsTask, QgsApplication
class MyTask(QgsTask):
def __init__(self, description, layer):
super().__init__(description, QgsTask.CanCancel)
self.layer = layer
self.result = None
def run(self):
total = self.layer.featureCount()
for i, feature in enumerate(self.layer.getFeatures()):
if self.isCanceled():
return False
# 処理
self.process_feature(feature)
self.setProgress(i / total * 100)
return True
def finished(self, result):
if result:
iface.messageBar().pushMessage("完了", "処理が終了しました", Qgis.Success)
else:
iface.messageBar().pushMessage("中断", "処理がキャンセルされました", Qgis.Warning)
def process_feature(self, feature):
# 実際の処理
pass
# タスクを実行
task = MyTask("処理中", layer)
QgsApplication.taskManager().addTask(task)
実践的なプラグイン例
CSV→GISレイヤ変換プラグイン
python
import csv
from qgis.core import (
QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY,
QgsField, QgsProject
)
from qgis.PyQt.QtCore import QVariant
class CsvToLayerPlugin:
def convert_csv_to_layer(self, csv_path, x_field, y_field, crs="EPSG:4326"):
# メモリレイヤを作成
layer = QgsVectorLayer(f"Point?crs={crs}", "csv_layer", "memory")
provider = layer.dataProvider()
# CSVを読み込んでフィールドを取得
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
fieldnames = reader.fieldnames
# フィールドを追加
fields = [QgsField(name, QVariant.String) for name in fieldnames]
provider.addAttributes(fields)
layer.updateFields()
# フィーチャを追加
f.seek(0)
next(reader) # ヘッダーをスキップ
for row in reader:
try:
x = float(row[x_field])
y = float(row[y_field])
feature = QgsFeature()
feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(x, y)))
feature.setAttributes([row[name] for name in fieldnames])
provider.addFeature(feature)
except (ValueError, KeyError) as e:
continue
# プロジェクトに追加
QgsProject.instance().addMapLayer(layer)
return layer
レイヤ情報エクスポートプラグイン
python
import json
class LayerInfoExporter:
def export_layer_info(self, layer, output_path):
info = {
"name": layer.name(),
"crs": layer.crs().authid(),
"feature_count": layer.featureCount(),
"geometry_type": layer.geometryType(),
"extent": {
"xmin": layer.extent().xMinimum(),
"ymin": layer.extent().yMinimum(),
"xmax": layer.extent().xMaximum(),
"ymax": layer.extent().yMaximum()
},
"fields": [
{
"name": field.name(),
"type": field.typeName()
}
for field in layer.fields()
]
}
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(info, f, indent=2, ensure_ascii=False)
return info
開発のベストプラクティス
コード構成
my_plugin/ ├── __init__.py ├── my_plugin.py # メインクラス ├── dialogs/ │ ├── __init__.py │ └── main_dialog.py # ダイアログクラス ├── processing/ │ ├── __init__.py │ └── algorithms.py # 処理アルゴリズム ├── utils/ │ ├── __init__.py │ └── helpers.py # ユーティリティ ├── resources/ │ ├── ui/ │ │ └── main_dialog.ui │ └── icons/ │ └── icon.png ├── metadata.txt └── README.md
テスト
python
# test_plugin.py
import unittest
class TestMyPlugin(unittest.TestCase):
def test_buffer_calculation(self):
# テストケース
result = calculate_buffer(100)
self.assertEqual(result, expected)
if __name__ == '__main__':
unittest.main()
まとめ
実践的なポイント
ポイント
- Processing Providerで処理ツールを実装
- プログレスバーで進捗を表示
- QgsSettingsで設定を永続化
- 適切なエラーハンドリング
- 長時間処理はQgsTaskで
次のステップ
おすすめ
- 公式ドキュメントを参照
- 既存プラグインのソースを読む
- 小さなプラグインから始める
関連記事
