Importante

La traduzione è uno sforzo comunitario you can join. Questa pagina è attualmente tradotta al 100.00%.

17. Scrivere un plugin di Processing

Dipendentemente dal tipo di plugin che stai per sviluppare, potrebbe essere una scelta conveniente di aggiungere le sue funzionalità quali algoritmo di Processing (o un set di essi). Ciò fornirebbe una migliore integrazione all’interno di QGIS, funzionalità aggiuntive (dal momento che può essere eseguito nei componenti di Processing, come il modellatore o l’interfaccia di elaborazione batch), e un tempo di sviluppo più spedito (dal momento che Processing si farà carico della gran parte del lavoro).

Per distribuire questi algoritmi, è necessario creare un nuovo plugin che li aggiunge al Processing Toolbox. Il plugin dovrebbe contenere un provider di algoritmi, che deve essere registrato quando il plugin viene istanziato.

17.1. Creazione da zero

Per creare un plugin da zero che contiene un provider di algoritmi, è possibile seguire questi passaggi utilizzando il Plugin Builder:

  1. Installare il plugin Plugin Builder

  2. Crea un nuovo plugin usando il Plugin Builder. Quando il Plugin Builder ti chiederà il modello da usare, seleziona «Sorgente di Processing».

  3. Il plugin creato contiene una sorgente con un singolo algoritmo. Il file della sorgente e dell’algoritmo sono entrambi commentati e contengono informazioni su come modificare la sorgente e gli algoritmi aggiuntivi. Fai riferimento ad essi per maggiori informazioni.

17.2. Aggiornare un plugin

Se si desidera aggiungere il plugin esistente a Processing, è necessario aggiungere un po” di codice.

  1. Nel tuo metadata.txt file, bisogna che aggiungi una variabile:

    hasProcessingProvider=yes
    
  2. Nel file Python dove il tuo plugin è impostato tramite il metodo initGui, bisogna adattare qualche linea in questo modo:

     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. Puoi creare una cartella processing_provider contenente tre files:

    • __init__.py con niente dentro. Ciò è necessario per produrre un package Python valido.

    • provider.py il quale creerà il Processing provider ed esporrà i tuoi algoritmi.

       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 che contiene il file algoritmo di esempio. Copia/incolla il contenuto del file script template file e modificalo conformemente alle tue esigenze.

    Dovresti avere un albero simile a questo:

    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. Ora puoi ricaricare il tuo plugin in QGIS e dovresti vedere il tuo script di esempio nella lista di Processing toolbox e modeler.

17.3. Implementazione di algoritmi di elaborazione personalizzati

17.3.1. Creazione di un algoritmo personalizzato

Ecco un semplice esempio di algoritmo buffer personalizzato:

 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. Personalizzazione della finestra di dialogo dell’algoritmo

Le finestre di dialogo personalizzate sono particolarmente utili quando si lavora con input annidati o dinamici, quando i parametri dipendono da fonti di dati esterne come le API (ad esempio menu a discesa popolati dinamicamente) o quando è necessaria una convalida avanzata e un comportamento di layout personalizzato non supportato dalla finestra di dialogo di elaborazione predefinita. Per sovrascrivere l’interfaccia utente predefinita (ad esempio per tipi di parametri complessi o logica dinamica), sottoclasse:class:QgsProcessingAlgorithmDialogBase <qgis.gui.QgsProcessingAlgorithmDialogBase>. Per eseguire il rendering dell’interfaccia utente personalizzata nella finestra di dialogo di elaborazione standard, è necessario chiamare self.setMainWidget(panel), dove panel è una classe:QgsPanelWidget <qgis.gui.QgsPanelWidget> contenente il layout personalizzato. Ciò garantisce che la tua interfaccia sia visualizzata correttamente e interagisca correttamente con il framework di elaborazione

Ecco un esempio che integra la gestione del segnale utilizzando QTimer per ingresso ritardato:

 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()

Per avviare la finestra di dialogo personalizzata per un dato algoritmo, è sufficiente istanziare CustomAlgorithmDialog con l’istanza dell’algoritmo ed eseguire exec():

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

17.3.3. Gestione Qt Signals

Quando si creano dialoghi reattivi, gestire attentamente le connessioni del segnale. Il modello sopra descritto utilizza un QTimer per annullare l’input dal campo di testo, impedendo chiamate ripetute e rapide. Ciò è particolarmente utile quando si recuperano metadati o si aggiornano elementi dell’interfaccia utente in base all’input dell’utente. Collegare sempre i segnali una volta (in genere in __init__) e utilizzare singleShot=True per garantire che lo slot venga attivato una sola volta dopo un ritardo.