PyQGIS · Material de apoyo

Ejercicio 1: Clases de pendiente

Este ejercicio convierte un modelo digital de elevación (DEM) en una capa raster con 6 clases de pendiente, calculando primero el porcentaje de inclinación del terreno y luego agrupándolo en rangos.

Conceptos clave de POO y PyQGIS usados en el ejercicio

  • Herencia de clase: class ClasesPendiente(QgsProcessingAlgorithm): crea una nueva herramienta reutilizando toda la infraestructura que QGIS ya ofrece para algoritmos de procesamiento (validación de parámetros, aparición en la Caja de herramientas, manejo de progreso, etc.).
  • Anotaciones de tipo (type hints): Optional[dict[str, Any]] no cambia el comportamiento del programa, pero documenta qué tipo de dato se espera recibir; ayuda a otros programadores (y a ti mismo en el futuro) a entender el código sin tener que ejecutarlo.
  • Parámetros de entrada/salida: QgsProcessingParameterRasterLayer y QgsProcessingParameterRasterDestination son clases que representan, respectivamente, "una capa raster que el usuario debe elegir" y "el archivo raster de salida que se generará".
  • Retroalimentación multi-paso: QgsProcessingMultiStepFeedback(2, model_feedback) reparte la barra de progreso entre los 2 sub-procesos internos del algoritmo, para que el usuario vea un avance proporcional y no saltos confusos.
  • Ejecutar herramientas nativas desde código: processing.run(...) permite invocar cualquier herramienta ya existente de QGIS (como native:slope) como si fuera una función de Python, encadenando su resultado hacia el siguiente paso.

Análisis y explicación de la estructura y lógica del código

El script completo tiene esta forma general (clase con 6 métodos obligatorios/estándar que QGIS espera encontrar):

01_clases_pendiente.py
"""
Model exported as python.
Name : Clases pendiente
Group : PASIG II
With QGIS : 34412
"""

from typing import Any, Optional

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingContext
from qgis.core import QgsProcessingFeedback, QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterRasterLayer
from qgis.core import QgsProcessingParameterRasterDestination
from qgis import processing


class ClasesPendiente(QgsProcessingAlgorithm):

    def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
        self.addParameter(QgsProcessingParameterRasterLayer('elevacin_dem', 'Elevación (DEM)', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterDestination('CapaConClaseDePendiente', 'Capa con clase de pendiente', createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters: dict[str, Any], context: QgsProcessingContext, model_feedback: QgsProcessingFeedback) -> dict[str, Any]:
        feedback = QgsProcessingMultiStepFeedback(2, model_feedback)
        results = {}
        outputs = {}

        # Pendiente
        alg_params = {
            'INPUT': parameters['elevacin_dem'],
            'Z_FACTOR': 1,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['Pendiente'] = processing.run('native:slope', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Clases de pendiente
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': outputs['Pendiente']['OUTPUT'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['0','3','1','3','6','2','6','15','3','15','30','4','30','45','5','45','9999','6'],
            'OUTPUT': parameters['CapaConClaseDePendiente']
        }
        outputs['ClasesDePendiente'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['CapaConClaseDePendiente'] = outputs['ClasesDePendiente']['OUTPUT']
        return results

    def name(self) -> str:
        return 'Clases pendiente'

    def displayName(self) -> str:
        return 'Clases pendiente'

    def group(self) -> str:
        return 'PASIG II'

    def groupId(self) -> str:
        return 'PASIG II'

    def createInstance(self):
        return self.__class__()

Sección 1 · Encabezado e importaciones

"""
Model exported as python.
Name : Clases pendiente
Group : PASIG II
With QGIS : 34412
"""

from typing import Any, Optional

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingContext
from qgis.core import QgsProcessingFeedback, QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterRasterLayer
from qgis.core import QgsProcessingParameterRasterDestination
from qgis import processing
  • """ ... """: es un comentario de varias líneas (docstring) que documenta el nombre del modelo, el grupo al que pertenece y la versión de QGIS con la que fue exportado. No afecta el funcionamiento del programa.
  • from typing import Any, Optional: importa herramientas para anotaciones de tipo. Any significa "cualquier tipo de dato" y Optional indica "este valor puede ser de tal tipo o puede ser None".
  • from qgis.core import QgsProcessing: importa la clase base con constantes útiles, como QgsProcessing.TEMPORARY_OUTPUT (resultado temporal).
  • from qgis.core import QgsProcessingAlgorithm: importa la clase de la que heredará nuestra herramienta, para que QGIS la reconozca como un algoritmo de procesamiento.
  • from qgis.core import QgsProcessingContext: importa la clase que representa el "contexto" de ejecución (configuración del proyecto, sistema de coordenadas, etc.).
  • from qgis.core import QgsProcessingFeedback, QgsProcessingMultiStepFeedback: importa las clases usadas para reportar el progreso y permitir cancelar la ejecución.
  • from qgis.core import QgsProcessingParameterRasterLayer: importa la clase usada para declarar un parámetro de entrada que sea una capa raster.
  • from qgis.core import QgsProcessingParameterRasterDestination: importa la clase usada para declarar el archivo raster de salida que generará la herramienta.
  • from qgis import processing: importa el módulo que permite ejecutar cualquier herramienta de la Caja de herramientas mediante processing.run(...).

Sección 2 · Declaración de la clase y sus parámetros

class ClasesPendiente(QgsProcessingAlgorithm):

    def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
        self.addParameter(QgsProcessingParameterRasterLayer('elevacin_dem', 'Elevación (DEM)', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterDestination('CapaConClaseDePendiente', 'Capa con clase de pendiente', createByDefault=True, defaultValue=None))
  • class ClasesPendiente(QgsProcessingAlgorithm):: declara una nueva clase llamada ClasesPendiente que hereda de QgsProcessingAlgorithm. Gracias a esta herencia, QGIS sabe que este objeto es "una herramienta de procesamiento".
  • def initAlgorithm(self, config: Optional[dict[str, Any]] = None):: define el método donde se declaran los parámetros. self hace referencia al propio objeto; config es opcional y casi nunca se usa directamente.
  • self.addParameter(QgsProcessingParameterRasterLayer('elevacin_dem', 'Elevación (DEM)', defaultValue=None)): agrega un parámetro de entrada de tipo capa raster. 'elevacin_dem' es el identificador interno (la "llave") y 'Elevación (DEM)' es el nombre visible para el usuario en QGIS.
  • self.addParameter(QgsProcessingParameterRasterDestination(...)): agrega el parámetro de salida. createByDefault=True indica que, si el usuario no especifica dónde guardar el resultado, QGIS creará un archivo temporal automáticamente.

Sección 3 · Inicio de processAlgorithm

    def processAlgorithm(self, parameters: dict[str, Any], context: QgsProcessingContext, model_feedback: QgsProcessingFeedback) -> dict[str, Any]:
        feedback = QgsProcessingMultiStepFeedback(2, model_feedback)
        results = {}
        outputs = {}
  • def processAlgorithm(self, parameters, context, model_feedback) -> dict:: este es el método donde ocurre realmente el procesamiento. Recibe los valores que el usuario eligió (parameters), el contexto de ejecución y el objeto de retroalimentación de progreso.
  • feedback = QgsProcessingMultiStepFeedback(2, model_feedback): crea un "envoltorio" de progreso que sabe que el modelo tiene 2 pasos internos, para repartir la barra de progreso entre ambos.
  • results = {}: diccionario vacío donde se guardará lo que el método debe devolver al final (los resultados "oficiales" de la herramienta).
  • outputs = {}: diccionario vacío donde se irán guardando los resultados de cada sub-proceso interno, para poder usarlos como entrada del siguiente paso.

Sección 4 · Paso 1: calcular la pendiente

        # Pendiente
        alg_params = {
            'INPUT': parameters['elevacin_dem'],
            'Z_FACTOR': 1,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['Pendiente'] = processing.run('native:slope', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}
  • # Pendiente: comentario que describe qué hace el bloque siguiente (no se ejecuta, solo documenta).
  • alg_params = {...}: crea un diccionario con los parámetros que necesita la herramienta nativa de pendiente.
  • 'INPUT': parameters['elevacin_dem']: usa como entrada la capa DEM que el usuario seleccionó (recuerda la "llave" elevacin_dem declarada en initAlgorithm).
  • 'Z_FACTOR': 1: factor de exageración vertical; 1 significa que no se aplica ninguna exageración.
  • 'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT: el resultado de este paso se guarda en un archivo temporal, porque todavía no es el resultado final del modelo.
  • outputs['Pendiente'] = processing.run('native:slope', alg_params, ...): ejecuta la herramienta nativa native:slope (equivalente a "Pendiente" en el menú de QGIS) y guarda su resultado en el diccionario outputs bajo la llave 'Pendiente'.
  • feedback.setCurrentStep(1): le indica a la barra de progreso que ya se completó el primer paso (de 2 totales).
  • if feedback.isCanceled(): return {}: si el usuario canceló la ejecución, se detiene el algoritmo inmediatamente devolviendo un diccionario vacío.

Sección 5 · Paso 2: reclasificar la pendiente en 6 clases

        # Clases de pendiente
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': outputs['Pendiente']['OUTPUT'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['0','3','1','3','6','2','6','15','3','15','30','4','30','45','5','45','9999','6'],
            'OUTPUT': parameters['CapaConClaseDePendiente']
        }
        outputs['ClasesDePendiente'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['CapaConClaseDePendiente'] = outputs['ClasesDePendiente']['OUTPUT']
        return results
  • 'DATA_TYPE': 3: indica que el raster de salida usará el tipo de dato "Int32" (números enteros de 32 bits), suficiente para guardar valores de clase del 1 al 6.
  • 'INPUT_RASTER': outputs['Pendiente']['OUTPUT']: usa como entrada el resultado del paso anterior (la capa de pendiente calculada), en vez del DEM original.
  • 'NODATA_FOR_MISSING': False: si un valor no cae en ningún rango de la tabla, NO se convertirá automáticamente en "sin datos".
  • 'NO_DATA': -9999: valor numérico que representará "sin datos" en el raster de salida.
  • 'RANGE_BOUNDARIES': 1: define cómo se interpretan los límites de cada rango de la tabla (mínimo incluido, máximo excluido).
  • 'RASTER_BAND': 1: la reclasificación se aplica sobre la primera banda del raster de entrada.
  • 'TABLE': [...]: lista de valores agrupados de 3 en 3 (mínimo, máximo, nueva clase). Por ejemplo, '0','3','1' significa "de 0% a 3% de pendiente, asignar la clase 1".
  • 'OUTPUT': parameters['CapaConClaseDePendiente']: a diferencia del paso 1, aquí el resultado se guarda directamente en el destino final elegido por el usuario, porque es el último paso del modelo.
  • outputs['ClasesDePendiente'] = processing.run(...): ejecuta la herramienta native:reclassifybytable con los parámetros anteriores.
  • results['CapaConClaseDePendiente'] = outputs['ClasesDePendiente']['OUTPUT']: guarda la ruta del archivo de salida en el diccionario de resultados oficiales que la herramienta debe entregar.
  • return results: termina el método devolviendo los resultados finales a QGIS.

Sección 6 · Métodos de identificación de la herramienta

    def name(self) -> str:
        return 'Clases pendiente'

    def displayName(self) -> str:
        return 'Clases pendiente'

    def group(self) -> str:
        return 'PASIG II'

    def groupId(self) -> str:
        return 'PASIG II'

    def createInstance(self):
        return self.__class__()
  • def name(self) -> str: return 'Clases pendiente': devuelve el identificador interno único de la herramienta (usado internamente por QGIS, sin espacios ni acentos idealmente).
  • def displayName(self) -> str: return 'Clases pendiente': devuelve el nombre que verá el usuario en la interfaz de QGIS.
  • def group(self) -> str: return 'PASIG II': devuelve el nombre del grupo/carpeta donde aparecerá la herramienta dentro de la Caja de herramientas.
  • def groupId(self) -> str: return 'PASIG II': devuelve el identificador interno de ese mismo grupo.
  • def createInstance(self): return self.__class__(): le dice a QGIS cómo crear una nueva copia de esta herramienta cada vez que se necesite ejecutar (patrón típico de fábrica de objetos en POO).
Pon a prueba lo analizado
Volver a PASIG II