PyQGIS · Material de apoyo

Ejercicio 3: Zonas oyamel

Este es el ejercicio más elaborado de los tres: identifica las zonas potenciales para el crecimiento del oyamel combinando 4 variables ambientales (suelo, elevación, precipitación y temperatura), cada una reclasificada según su propio rango óptimo, y luego combinadas todas con operaciones booleanas "AND".

Conceptos clave de POO y PyQGIS usados en el ejercicio

  • Cuatro parámetros de entrada: initAlgorithm declara elevacin, precipitacin, suelo y temperatura, mostrando que un algoritmo puede combinar tantas fuentes de datos como sea necesario para el análisis.
  • Feedback de 7 pasos: QgsProcessingMultiStepFeedback(7, model_feedback) porque hay 4 reclasificaciones + 3 combinaciones booleanas = 7 sub-procesos en total. Cuantos más pasos tiene un modelo, más importante es este control de progreso para el usuario.
  • Combinación en cascada (patrón "árbol"): primero se combinan pares de capas (temperatura+suelo, elevación+precipitación) y después se combina el resultado de esos dos pares en una unión final. Este patrón de "ir combinando de dos en dos" es común cuando se necesitan cumplir varias condiciones simultáneamente.
  • Reutilización de un mismo patrón de código: las 4 reclasificaciones usan una estructura casi idéntica (mismo tipo de parámetros), cambiando solo la capa de entrada y los rangos de la tabla; identificar este tipo de "plantilla repetida" ayuda a leer scripts largos más rápido.

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

03_zonas_oyamel.py
"""
Model exported as python.
Name : Oyamel
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 Oyamel(QgsProcessingAlgorithm):

    def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
        self.addParameter(QgsProcessingParameterRasterLayer('elevacin', 'Elevación', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterLayer('precipitacin', 'Precipitación', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterLayer('suelo', 'Suelo', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterLayer('temperatura', 'Temperatura', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterDestination('ZonasPotenciales', 'Zonas potenciales', createByDefault=True, defaultValue=None))

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

        # Reclasificar Suelo
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': parameters['suelo'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['1','4','1','4','99','0'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReclasificarSuelo'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Reclasificar Elevación
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': parameters['elevacin'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['0','2800','0','2800','3500','1','3500','999999','0'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReclasificarElevacin'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Reclasificar Precipitación
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': parameters['precipitacin'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['0','1000','0','1000','999999','1'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReclasificarPrecipitacin'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Reclasificar Tempetura
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': parameters['temperatura'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['-99','-12','0','-12','15','1','15','99','0'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReclasificarTempetura'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Unión Temperatura y Suelo
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT': [outputs['ReclasificarTempetura']['OUTPUT'],outputs['ReclasificarSuelo']['OUTPUT']],
            'NODATA_AS_FALSE': False,
            'NO_DATA': -9999,
            'REF_LAYER': outputs['ReclasificarTempetura']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['UninTemperaturaYSuelo'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Union Elevacion y Precipitacón
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT': [outputs['ReclasificarElevacin']['OUTPUT'],outputs['ReclasificarPrecipitacin']['OUTPUT']],
            'NODATA_AS_FALSE': False,
            'NO_DATA': -9999,
            'REF_LAYER': outputs['ReclasificarElevacin']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['UnionElevacionYPrecipitacn'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Unión final
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT': [outputs['UnionElevacionYPrecipitacn']['OUTPUT'],outputs['UninTemperaturaYSuelo']['OUTPUT']],
            'NODATA_AS_FALSE': False,
            'NO_DATA': -9999,
            'REF_LAYER': outputs['UnionElevacionYPrecipitacn']['OUTPUT'],
            'OUTPUT': parameters['ZonasPotenciales']
        }
        outputs['UninFinal'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['ZonasPotenciales'] = outputs['UninFinal']['OUTPUT']
        return results

    def name(self) -> str:
        return 'Oyamel'

    def displayName(self) -> str:
        return 'Oyamel'

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

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

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

Sección 1 · Encabezado, importaciones y parámetros de entrada

class Oyamel(QgsProcessingAlgorithm):

    def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
        self.addParameter(QgsProcessingParameterRasterLayer('elevacin', 'Elevación', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterLayer('precipitacin', 'Precipitación', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterLayer('suelo', 'Suelo', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterLayer('temperatura', 'Temperatura', defaultValue=None))
        self.addParameter(QgsProcessingParameterRasterDestination('ZonasPotenciales', 'Zonas potenciales', createByDefault=True, defaultValue=None))
  • class Oyamel(QgsProcessingAlgorithm):: declara la clase heredando de QgsProcessingAlgorithm, igual que en los ejercicios anteriores.
  • self.addParameter(QgsProcessingParameterRasterLayer('elevacin', 'Elevación', ...)): primer parámetro de entrada, la capa de elevación.
  • self.addParameter(QgsProcessingParameterRasterLayer('precipitacin', 'Precipitación', ...)): segundo parámetro, la capa de precipitación.
  • self.addParameter(QgsProcessingParameterRasterLayer('suelo', 'Suelo', ...)): tercer parámetro, la capa de suelo.
  • self.addParameter(QgsProcessingParameterRasterLayer('temperatura', 'Temperatura', ...)): cuarto parámetro, la capa de temperatura. En total son 4 capas de entrada, más que en cualquier ejercicio anterior.
  • self.addParameter(QgsProcessingParameterRasterDestination('ZonasPotenciales', ...)): declara el archivo de salida final.

Sección 2 · Inicio de processAlgorithm

    def processAlgorithm(self, parameters, context, model_feedback):
        feedback = QgsProcessingMultiStepFeedback(7, model_feedback)
        results = {}
        outputs = {}
  • feedback = QgsProcessingMultiStepFeedback(7, model_feedback): se declaran 7 pasos, porque el modelo realiza 4 reclasificaciones + 3 combinaciones booleanas = 7 sub-procesos en total.
  • results = {} / outputs = {}: los mismos diccionarios vacíos vistos en los ejercicios anteriores.

Sección 3 · Pasos 1 a 4: reclasificar cada variable ambiental

Las 4 reclasificaciones siguen exactamente el mismo patrón; solo cambian la capa de entrada, la tabla de rangos y la llave donde se guarda el resultado:

        # Reclasificar Suelo
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT_RASTER': parameters['suelo'],
            'NODATA_FOR_MISSING': False,
            'NO_DATA': -9999,
            'RANGE_BOUNDARIES': 1,  # min <= valor < max
            'RASTER_BAND': 1,
            'TABLE': ['1','4','1','4','99','0'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ReclasificarSuelo'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}
  • 'INPUT_RASTER': parameters['suelo']: usa la capa de suelo proporcionada por el usuario.
  • 'TABLE': ['1','4','1','4','99','0']: las clases de suelo de 1 a 4 se marcan como aptas (clase 1); de 4 a 99, como no aptas (clase 0).
  • outputs['ReclasificarSuelo'] = processing.run(...): guarda el resultado bajo la llave 'ReclasificarSuelo', que se usará más adelante.
  • feedback.setCurrentStep(1): marca el primero de los 7 pasos como completado.

Las siguientes 3 reclasificaciones repiten la misma estructura, cambiando solo estos valores:

        # Reclasificar Elevación (paso 2)
        'INPUT_RASTER': parameters['elevacin']
        'TABLE': ['0','2800','0','2800','3500','1','3500','999999','0']
        # 0-2800 m -> clase 0 | 2800-3500 m -> clase 1 (apta) | 3500+ -> clase 0

        # Reclasificar Precipitación (paso 3)
        'INPUT_RASTER': parameters['precipitacin']
        'TABLE': ['0','1000','0','1000','999999','1']
        # 0-1000 mm -> clase 0 | más de 1000 mm -> clase 1 (apta)

        # Reclasificar Temperatura (paso 4)
        'INPUT_RASTER': parameters['temperatura']
        'TABLE': ['-99','-12','0','-12','15','1','15','99','0']
        # menor a -12°C -> clase 0 | entre -12°C y 15°C -> clase 1 (apta) | mayor a 15°C -> clase 0

Cada tabla refleja un requerimiento ecológico distinto del oyamel: altitudes elevadas, precipitación abundante y temperaturas frías. Después de cada reclasificación se llama a feedback.setCurrentStep(n) con el número de paso correspondiente (2, 3 y 4).

Sección 4 · Pasos 5 y 6: combinar en pares

        # Unión Temperatura y Suelo
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT': [outputs['ReclasificarTempetura']['OUTPUT'],outputs['ReclasificarSuelo']['OUTPUT']],
            'NODATA_AS_FALSE': False,
            'NO_DATA': -9999,
            'REF_LAYER': outputs['ReclasificarTempetura']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['UninTemperaturaYSuelo'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

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

        # Union Elevacion y Precipitacón
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT': [outputs['ReclasificarElevacin']['OUTPUT'],outputs['ReclasificarPrecipitacin']['OUTPUT']],
            'NODATA_AS_FALSE': False,
            'NO_DATA': -9999,
            'REF_LAYER': outputs['ReclasificarElevacin']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['UnionElevacionYPrecipitacn'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(6)
        if feedback.isCanceled():
            return {}
  • 'INPUT': [outputs['ReclasificarTempetura']['OUTPUT'], outputs['ReclasificarSuelo']['OUTPUT']]: combina las capas reclasificadas de temperatura y suelo en una sola lista de entrada.
  • outputs['UninTemperaturaYSuelo'] = processing.run('native:rasterbooleanand', ...): ejecuta el "Y" lógico; el resultado vale 1 solo donde ambas capas valen 1 (temperatura Y suelo aptos).
  • feedback.setCurrentStep(5): marca el paso 5 como completado.
  • El segundo bloque repite la misma lógica pero combinando ReclasificarElevacin y ReclasificarPrecipitacin, guardando el resultado en outputs['UnionElevacionYPrecipitacn'] y marcando el paso 6.

En vez de combinar las 4 capas en un solo paso, el modelo las agrupa en dos pares lógicos. El resultado final sería el mismo si se combinaran las 4 capas de una sola vez, pero dividirlo en pares facilita revisar resultados intermedios si algo sale mal.

Sección 5 · Paso 7: unión final

        # Unión final
        alg_params = {
            'DATA_TYPE': 3,  # Int32
            'INPUT': [outputs['UnionElevacionYPrecipitacn']['OUTPUT'],outputs['UninTemperaturaYSuelo']['OUTPUT']],
            'NODATA_AS_FALSE': False,
            'NO_DATA': -9999,
            'REF_LAYER': outputs['UnionElevacionYPrecipitacn']['OUTPUT'],
            'OUTPUT': parameters['ZonasPotenciales']
        }
        outputs['UninFinal'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['ZonasPotenciales'] = outputs['UninFinal']['OUTPUT']
        return results
  • 'INPUT': [outputs['UnionElevacionYPrecipitacn']['OUTPUT'], outputs['UninTemperaturaYSuelo']['OUTPUT']]: combina los dos resultados de pares anteriores (no las 4 capas originales directamente).
  • 'OUTPUT': parameters['ZonasPotenciales']: esta vez sí se guarda en el destino final elegido por el usuario, porque es el último paso del modelo.
  • outputs['UninFinal'] = processing.run('native:rasterbooleanand', ...): un píxel del resultado solo valdrá 1 si las 4 condiciones ambientales se cumplen simultáneamente en ese punto.
  • results['ZonasPotenciales'] = outputs['UninFinal']['OUTPUT'] y return results: entrega el resultado final a QGIS.

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

    def name(self) -> str:
        return 'Oyamel'

    def displayName(self) -> str:
        return 'Oyamel'

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

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

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

Igual que en los ejercicios 1 y 2, estos métodos identifican la herramienta ante QGIS y le indican cómo crear nuevas instancias de la clase.

Pon a prueba lo analizado
Volver a PASIG II