Importante

La traducción es un esfuerzo comunitario puede unirse. Esta página está actualmente traducida en |progreso de traducción|.

17. Escribir nuevos complementos de procesamiento

Dependiendo del tipo de complemento que vaya a desarrollar, podría ser una mejor opción agregar su funcionalidad como un algoritmo de procesamiento (o un conjunto de ellos). Eso proporcionaría una mejor integración dentro de QGIS, funcionalidad adicional (ya que se puede ejecutar en los componentes de Processing, como el modelador o la interfaz de procesamiento por lotes), y un tiempo de desarrollo más rápido (ya que Processing tomará una gran parte del trabajo).

Para distribuir esos algoritmos, debe crear un nuevo complemento que los agregue a Caja de Herramientas de Procesos. El complemento debe contener un proveedor de algoritmos, que debe registrarse cuando se crea una instancia del complemento.

17.1. Creando desde cero

Para crear un complemento desde cero que contenga un proveedor de algoritmos, puede seguir estos pasos utilizando el Generador de complementos:

  1. Instala el complemento Plugin Builder

  2. Crea un nuevo complemento, usando el Plugin Builder. En el cuadro de diálogo del Plugin Builder, selecciona «Processing provider».

  3. El complemento creado contiene un proveedor con un solo algoritmo. Tanto el archivo del proveedor como el archivo del algoritmo están completamente comentados y contienen información sobre cómo modificar el proveedor y agregar algoritmos adicionales. Consúltelos para obtener más información.

17.2. Actuializar un complemento

Si quiere añadir su complemento existente a Procesos, necesitará añadir algo de código.

  1. En su archivo metadata.txt, necesitará añadir una variable:

    hasProcessingProvider=yes
    
  2. En el archivo Python donde tu complemento está instalado con el método initGui, necesitas adaptar algunas líneas como esta:

     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. Puedes crear una carpeta processing_provider con tres archivos en ella:

    • __init__.py sin nada en él. Esto es necesario para crear un paquete Python válido.

    • provider.py que creará el proveedor de procesamiento y expondrá sus algoritmos.

       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 el cuál contiene el archivo de ejemplo de algoritmo. Copiar/pegar el contenido de la fuente archivo de plantilla de script y actualizalo de acuerdo a tus necesidades.

    Debería tener un árbol similar a este:

    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. Ahora puede volver a cargar su complemento en QGIS y debería ver su script de ejemplo en la caja de herramientas de procesamiento y el modelador.

17.3. Implementación de algoritmos de procesamiento personalizados

17.3.1. Creación de un algoritmo personalizado

Aquí hay un ejemplo sencillo de un algoritmo de búfer personalizado:

 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. Personalización del cuadro de diálogo del algoritmo

Los cuadros de diálogo personalizados son especialmente útiles cuando se trabaja con entradas anidadas o dinámicas, cuando los parámetros dependen de fuentes de datos externas como API (por ejemplo, menús desplegables rellenados dinámicamente) o cuando se necesita una validación avanzada y un comportamiento de diseño personalizado que no es compatible con el cuadro de diálogo de procesamiento predeterminado. Para anular la interfaz de usuario predeterminada (por ejemplo, para tipos de parámetros complejos o lógica dinámica), cree una subclase de QgsProcessingAlgorithmDialogBase. Para renderizar su interfaz de usuario personalizada en la ventana del cuadro de diálogo Procesamiento estándar, debe llamar a self.setMainWidget(panel), donde panel es un QgsPanelWidget que contiene su diseño personalizado. Esto garantiza que su interfaz se muestre correctamente e interactúe adecuadamente con el marco de trabajo de Procesamiento.

Aquí hay un ejemplo que integra la gestión de señales utilizando QTimer para entradas sin rebote:

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

Para iniciar el cuadro de diálogo personalizado para un algoritmo determinado, simplemente instancie CustomAlgorithmDialog con su instancia de algoritmo y llame a exec():

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

17.3.3. Gestión de señales Qt

Al crear diálogos reactivos, gestione cuidadosamente las conexiones de señales. El patrón anterior utiliza un QTimer para eliminar el rebote de la entrada del campo de texto, lo que evita llamadas repetidas rápidas. Esto resulta especialmente útil al recuperar metadatos o actualizar elementos de la interfaz de usuario basados en la entrada del usuario. Conecte siempre las señales una vez (normalmente en __init__) y utilice singleShot=True para garantizar que la ranura solo se active una vez tras un retraso.