重要

翻訳は あなたが参加できる コミュニティの取り組みです。このページは現在 64.29% 翻訳されています。

17. プロセシングプラグインを書く

開発しようとしているプラ​​グインの種類によっては、プロセシングアルゴリズム(またはそれらのセット)として機能を追加する方が良い場合もあるでしょう。そうすれば、QGIS内でのより良い統合がなされ(これは、モデラーやバッチ処理インタフェースといった、「プロセシング」のコンポーネントの中で実行できるためです)、また開発時間の短縮も期待できます(「プロセシング」が作業の大部分を肩代わりしてくれるからです)。

開発したアルゴリズムを配布するためには、アルゴリズムをプロセシングツールボックスに追加するためのプラグインを新しく作る必要があります。このプラグインにはアルゴリズムプロバイダを含ませるとともに、プラグインの初期化の際にアルゴリズムがツールボックスに登録されるようにする必要があります。

17.1. イチから作る

アルゴリズムプロバイダを含むプラグインをイチから作るには、Plugin Builder を使って以下のステップに従います。

  1. Plugin Builder プラグインをインストールする

  2. Plugin Builder を使用して新しくプラグインを作成します。Plugin Builder が使用するテンプレートをきいてきたら、「プロセシングプロバイダ」を選択します。

  3. 生成されたプラグインには、アルゴリズムをひとつ持つプロバイダが含まれています。プロバイダファイルおよびアルゴリズムファイルには両方ともに十分なコメントがついていて、プロバイダを修正したりさらにアルゴリズムを追加する方法についての情報が含まれています。詳細については、それらを参照してください。

17.2. プラグインをアップデートする

すでに作成済みのプラグインをプロセッシングに追加したい場合は、さらにコードを追加する必要があります。

  1. metadata.txt ファイルに以下の変数を追加する必要があります。

    hasProcessingProvider=yes
    
  2. initGui メソッドによってプラグインのセットアップを担うPythonファイルでは、幾つかのコードを直す必要があります。

     1from qgis.core import QgsApplication
     2from .processing_provider.provider import Provider
     3
     4class YourPluginName:
     5
     6    def __init__(self):
     7        self.provider = None
     8
     9    def initProcessing(self):
    10        self.provider = Provider()
    11        QgsApplication.processingRegistry().addProvider(self.provider)
    12
    13    def initGui(self):
    14        self.initProcessing()
    15
    16    def unload(self):
    17        QgsApplication.processingRegistry().removeProvider(self.provider)
    
  3. processing_provider フォルダを作ってそこに次の3つのファイルを納めることもできます。

    • 白紙の __init__.py ファイル。このファイルは妥当なPythonパッケージを作るために必要です。

    • provider.py ファイルはプロセッシングプロバイダを生成しあなたのアルゴリズムを外部から使えるようにします。

       1from qgis.core import QgsProcessingProvider
       2from qgis.PyQt.QtGui import QIcon
       3
       4from .example_processing_algorithm import ExampleProcessingAlgorithm
       5
       6class Provider(QgsProcessingProvider):
       7
       8    """ The provider of our plugin. """
       9
      10    def loadAlgorithms(self):
      11        """ Load each algorithm into the current provider. """
      12        self.addAlgorithm(ExampleProcessingAlgorithm())
      13        # add additional algorithms here
      14        # self.addAlgorithm(MyOtherAlgorithm())
      15
      16    def id(self) -> str:
      17        """The ID of your plugin, used for identifying the provider.
      18
      19        This string should be a unique, short, character only string,
      20        eg "qgis" or "gdal". This string should not be localised.
      21        """
      22        return 'yourplugin'
      23
      24    def name(self) -> str:
      25        """The human friendly name of your plugin in Processing.
      26
      27        This string should be as short as possible (e.g. "Lastools", not
      28        "Lastools version 1.0.1 64-bit") and localised.
      29        """
      30        return self.tr('Your plugin')
      31
      32    def icon(self) -> QIcon:
      33        """Should return a QIcon which is used for your provider inside
      34        the Processing toolbox.
      35        """
      36        return QgsProcessingProvider.icon(self)
      
    • example_processing_algorithm.py ファイルはサンプルアルゴリズムを含みます。 script template file の内容をコピー&ペーストして、自分の必要に合わせて修正してください。

    You should have a tree similar to this:

    1└── your_plugin_root_folder
    2   ├── __init__.py
    3   ├── LICENSE
    4   ├── metadata.txt
    5   └── processing_provider
    6         ├── example_processing_algorithm.py
    7         ├── __init__.py
    8         └── provider.py
    
  4. ここまできたらQGISでプラグインをリロードすれば、プロセッシングツールボックスとモデラーの中にあなたのスクリプトを見つけることができるはずです。

17.3. Implementing custom Processing algorithms

17.3.1. Creating a custom algorithm

Here's a simple example of a custom buffer algorithm:

 1from qgis.core import (
 2    QgsProcessingAlgorithm,
 3    QgsProcessingParameterFeatureSource,
 4    QgsProcessingParameterNumber,
 5    QgsProcessingParameterFeatureSink,
 6    QgsFeatureSink,
 7)
 8
 9class BufferAlgorithm(QgsProcessingAlgorithm):
10
11    INPUT = 'INPUT'
12    DISTANCE = 'DISTANCE'
13    OUTPUT = 'OUTPUT'
14
15    def initAlgorithm(self, config=None):
16        self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, 'Input layer'))
17        self.addParameter(QgsProcessingParameterNumber(self.DISTANCE, 'Buffer distance', defaultValue=100.0))
18        self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, 'Output layer'))
19
20    def processAlgorithm(self, parameters, context, feedback):
21        source = self.parameterAsSource(parameters, self.INPUT, context)
22        distance = self.parameterAsDouble(parameters, self.DISTANCE, context)
23        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
24                                               source.fields(), source.wkbType(), source.sourceCrs())
25
26        for f in source.getFeatures():
27            f.setGeometry(f.geometry().buffer(distance, 5))
28            sink.addFeature(f, QgsFeatureSink.FastInsert)
29
30        return {self.OUTPUT: dest_id}
31
32    def name(self):
33        return 'buffer'
34
35    def displayName(self):
36        return 'Buffer Features'
37
38    def group(self):
39        return 'Examples'
40
41    def groupId(self):
42        return 'examples'
43
44    def createInstance(self):
45        return BufferAlgorithm()

17.3.2. Customizing the algorithm dialog

Custom dialogs are especially useful when working with nested or dynamic inputs, when parameters depend on external data sources such as APIs (e.g. dynamically populated dropdowns), or when you need advanced validation and custom layout behavior that isn’t supported by the default Processing dialog. To override the default UI (e.g. for complex parameter types or dynamic logic), subclass QgsProcessingAlgorithmDialogBase. To render your custom UI in the standard Processing dialog window, you must call self.setMainWidget(panel), where panel is a QgsPanelWidget containing your custom layout. This ensures your interface is correctly displayed and interacts properly with the Processing framework.

Here is an example that integrates signal management using QTimer for debounced input:

 1from qgis.PyQt.QtCore import Qt, QT_VERSION_STR, QTimer
 2from qgis.core import (
 3    QgsProcessingAlgorithm,
 4    QgsProcessingContext,
 5    QgsProcessingFeedback,
 6    Qgis,
 7)
 8from qgis.PyQt.QtWidgets import QWidget, QVBoxLayout, QLineEdit
 9from qgis import gui, processing
10from datetime import datetime
11from typing import Dict, Optional
12from osgeo import gdal
13
14class CustomAlgorithmDialog(gui.QgsProcessingAlgorithmDialogBase):
15    def __init__(
16        self,
17        algorithm: QgsProcessingAlgorithm,
18        parent: Optional[QWidget] = None,
19        title: Optional[str] = None,
20    ):
21        super().__init__(
22            parent,
23            flags=Qt.WindowFlags(),
24            mode=gui.QgsProcessingAlgorithmDialogBase.DialogMode.Single,
25        )
26        self.context = QgsProcessingContext()
27        self.setAlgorithm(algorithm)
28        self.setModal(True)
29        self.setWindowTitle(title or algorithm.displayName())
30
31        self.panel = gui.QgsPanelWidget()
32        layout = self.buildDialog()
33        self.panel.setLayout(layout)
34        self.setMainWidget(self.panel)
35
36        self.cancelButton().clicked.connect(self.reject)
37
38    def buildDialog(self) -> QVBoxLayout:
39        layout = QVBoxLayout()
40
41        self.input = QLineEdit()
42
43        # Set up a debounced signal using QTimer
44        self._update_timer = QTimer(self, singleShot=True)
45        self._update_timer.timeout.connect(self._on_collection_id_ready)
46        self.input.textChanged.connect(self._on_collection_id_changed)
47
48        layout.addWidget(self.input)
49
50        return layout
51
52    def _on_collection_id_changed(self):
53        self._update_timer.start(500)  # Debounce input
54
55    def _on_collection_id_ready(self):
56        self.pushInfo("Fetching metadata for collection ID…")
57
58    def getParameters(self) -> Dict:
59        try:
60            return {'DISTANCE': float(self.input.text())}
61        except ValueError:
62            raise ValueError("Invalid buffer distance")
63
64    def processingContext(self):
65        return self.context
66
67    def createFeedback(self):
68        return QgsProcessingFeedback()
69
70    def runAlgorithm(self):
71        context = self.processingContext()
72        feedback = self.createFeedback()
73        params = self.getParameters()
74
75        self.pushDebugInfo(f"QGIS version: {Qgis.QGIS_VERSION}")
76        self.pushDebugInfo(f"QGIS code revision: {Qgis.QGIS_DEV_VERSION}")
77        self.pushDebugInfo(f"Qt version: {QT_VERSION_STR}")
78        self.pushDebugInfo(f"GDAL version: {gdal.VersionInfo('--version')}")
79        self.pushCommandInfo(f"Algorithm started at: {datetime.now().isoformat(timespec='seconds')}")
80        self.pushCommandInfo(f"Algorithm '{self.algorithm().displayName()}' starting…")
81        self.pushCommandInfo("Input parameters:")
82        for k, v in params.items():
83            self.pushCommandInfo(f"  {k}: {v}")
84
85        results = processing.run(self.algorithm(), params, context=context, feedback=feedback)
86        self.setResults(results)
87        self.showLog()

To launch the custom dialog for a given algorithm, simply instantiate CustomAlgorithmDialog with your algorithm instance and call exec():

dlg = CustomAlgorithmDialog(BufferAlgorithm())
dlg.exec()

17.3.3. Managing Qt Signals

When building reactive dialogs, manage signal connections carefully. The above pattern uses a QTimer to debounce input from the text field, preventing rapid repeated calls. This is especially useful when fetching metadata or updating UI elements based on user input. Always connect signals once (typically in __init__) and use singleShot=True to ensure the slot is triggered only once after a delay.