중요

번역은 여러분이 참여할 수 있는 커뮤니티 활동입니다. 이 페이지는 현재 64.29% 번역되었습니다.

17. 공간 처리 플러그인 작성하기

여러분이 개발하려는 플러그인의 유형에 따라, 플러그인의 기능을 공간 처리 알고리즘으로 (또는 공간 처리 알고리즘 집합으로) 추가하는 편이 더 나을 수도 있습니다. QGIS 안에 더 잘 통합시킬 수 있고, (모델 작성자 또는 배치(batch) 공간 처리 인터페이스 같은 공간 처리 프레임워크 구성 요소에서 실행할 수 있기 때문에) 추가 기능을 제공할 수 있으며, (공간 처리 프레임워크가 작업의 상당 부분을 처리해줄 것이기 때문에) 개발 시간도 절약될 것입니다.

이런 알고리즘을 배포하려면, 공간 처리 툴박스에 알고리즘을 추가하는 새 플러그인을 생성해야 합니다. 이 플러그인은 알고리즘 제공자를 담고 있어야 하는데, 플러그인이 인스턴스화될 때 이 알고리즘 제공자를 등록해야 합니다.

17.1. 처음부터 생성하기

알고리즘 제공자를 담고 있는 플러그인을 처음부터 생성하려면, 플러그인 작성자(Plugin Builder)를 사용해서 다음 단계들을 따라가면 됩니다:

  1. 플러그인 작성자 플러그인을 설치하십시오.

  2. 플러그인 작성자를 사용해서 새 플러그인을 생성하십시오. 플러그인 작성자가 사용할 템플릿을 고르라고 할 때 “공간 처리 제공자(Processing provider)”를 선택하십시오.

  3. 생성된 플러그인은 단일 알고리즘을 가진 제공자를 담고 있습니다. 제공자 파일과 알고리즘 파일 둘 다 충분한 주석들과 제공자를 수정하고 추가적인 알고리즘을 추가하는 방법에 대한 정보를 담고 있습니다. 자세한 정보를 알고 싶다면 이 파일들을 참조하세요.

17.2. 플러그인을 업데이트하기

공간 처리 프레임워크에 여러분의 기존 플러그인을 추가하고 싶은 경우, 몇몇 코드를 추가해야 합니다.

  1. metadata.txt 파일에 다음 변수를 추가해야 합니다:

    hasProcessingProvider=yes
    
  2. initGui 메소드를 사용해서 플러그인을 초기 설정하는 파이썬 파일에서 몇 줄을 다음과 같이 조정해줘야 합니다:

     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 파일 — 이 파일은 무결한 파이썬 패키지를 만들기 위해 필요합니다.

    • 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 파일 — 이 파일은 예시 알고리즘 파일을 담습니다. 이 파일에 스크립트 템플릿 파일 의 내용을 복사/붙여넣기한 다음 여러분의 필요에 따라 업데이트하십시오.

    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.