Ejercicio 2: Zonas sorgo
Este ejercicio identifica las zonas aptas para el cultivo de sorgo, combinando dos condiciones: un tipo de suelo adecuado (arcilloso) y que no se encuentre en zona inundable. El resultado es una capa booleana (1 = zona apta, 0 = no apta).
Conceptos clave de POO y PyQGIS usados en el ejercicio
-
Múltiples parámetros de entrada: a diferencia del
ejercicio 1, aquí
initAlgorithmdeclara dos capas raster de entrada (elevacinysuelo), mostrando cómo un algoritmo puede combinar varias fuentes de información. -
Resultados intermedios encadenados: el diccionario
outputsguarda resultados temporales que luego se usan como entrada de otro paso, formando una cadena de procesamiento (patrón muy común en modelos de PyQGIS). -
Feedback de 3 pasos:
QgsProcessingMultiStepFeedback(3, model_feedback)porque el modelo ahora tiene 3 sub-procesos: reclasificar suelo, reclasificar zona inundable, y combinarlos con un "AND" booleano. -
Álgebra de mapas booleana:
native:rasterbooleanandcombina dos capas raster de 0/1 aplicando la operación lógica "Y": el resultado es 1 solo si ambas condiciones se cumplen al mismo tiempo.
Análisis y explicación de la estructura y lógica del código
"""
Model exported as python.
Name : Zonas Sorgo
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 ZonasSorgo(QgsProcessingAlgorithm):
def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
self.addParameter(QgsProcessingParameterRasterLayer('elevacin', 'Elevación', defaultValue=None))
self.addParameter(QgsProcessingParameterRasterLayer('suelo', 'Suelo', defaultValue=None))
self.addParameter(QgsProcessingParameterRasterDestination('ZonasSorgo', 'Zonas sorgo', createByDefault=True, defaultValue=None))
def processAlgorithm(self, parameters: dict[str, Any], context: QgsProcessingContext, model_feedback: QgsProcessingFeedback) -> dict[str, Any]:
feedback = QgsProcessingMultiStepFeedback(3, model_feedback)
results = {}
outputs = {}
# Clase arcilla
alg_params = {
'DATA_TYPE': 5, # Float32
'INPUT_RASTER': parameters['suelo'],
'NODATA_FOR_MISSING': False,
'NO_DATA': -9999,
'RANGE_BOUNDARIES': 1, # min <= valor < max
'RASTER_BAND': 1,
'TABLE': ['0','2','0','2','3','1','3','99','0'],
'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
}
outputs['ClaseArcilla'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
feedback.setCurrentStep(1)
if feedback.isCanceled():
return {}
# Clase zona inundable
alg_params = {
'DATA_TYPE': 5, # Float32
'INPUT_RASTER': parameters['elevacin'],
'NODATA_FOR_MISSING': False,
'NO_DATA': -9999,
'RANGE_BOUNDARIES': 1, # min <= valor < max
'RASTER_BAND': 1,
'TABLE': ['0','11','1','11','999','0'],
'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
}
outputs['ClaseZonaInundable'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
feedback.setCurrentStep(2)
if feedback.isCanceled():
return {}
# Zonas sorgo
alg_params = {
'DATA_TYPE': 3, # Int32
'INPUT': [outputs['ClaseArcilla']['OUTPUT'],outputs['ClaseZonaInundable']['OUTPUT']],
'NODATA_AS_FALSE': False,
'NO_DATA': -9999,
'REF_LAYER': outputs['ClaseZonaInundable']['OUTPUT'],
'OUTPUT': parameters['ZonasSorgo']
}
outputs['ZonasSorgo'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
results['ZonasSorgo'] = outputs['ZonasSorgo']['OUTPUT']
return results
def name(self) -> str:
return 'Zonas Sorgo'
def displayName(self) -> str:
return 'Zonas Sorgo'
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
class ZonasSorgo(QgsProcessingAlgorithm):
def initAlgorithm(self, config: Optional[dict[str, Any]] = None):
self.addParameter(QgsProcessingParameterRasterLayer('elevacin', 'Elevación', defaultValue=None))
self.addParameter(QgsProcessingParameterRasterLayer('suelo', 'Suelo', defaultValue=None))
self.addParameter(QgsProcessingParameterRasterDestination('ZonasSorgo', 'Zonas sorgo', createByDefault=True, defaultValue=None))
class ZonasSorgo(QgsProcessingAlgorithm):: declara la clase de la herramienta, heredando deQgsProcessingAlgorithmigual que en el ejercicio 1.self.addParameter(QgsProcessingParameterRasterLayer('elevacin', 'Elevación', ...)): primer parámetro de entrada: la capa de elevación.self.addParameter(QgsProcessingParameterRasterLayer('suelo', 'Suelo', ...)): segundo parámetro de entrada: la capa de tipo de suelo. A diferencia del ejercicio 1, aquí se combinan dos capas distintas.self.addParameter(QgsProcessingParameterRasterDestination('ZonasSorgo', ...)): declara el archivo de salida donde se guardará el resultado final (zonas aptas para sorgo).
Sección 2 · Inicio de processAlgorithm
def processAlgorithm(self, parameters: dict[str, Any], context: QgsProcessingContext, model_feedback: QgsProcessingFeedback) -> dict[str, Any]:
feedback = QgsProcessingMultiStepFeedback(3, model_feedback)
results = {}
outputs = {}
feedback = QgsProcessingMultiStepFeedback(3, model_feedback): a diferencia del ejercicio 1 (que usaba 2), aquí se declaran 3 pasos internos, porque el modelo tiene 3 sub-procesos.results = {}youtputs = {}: mismos diccionarios vacíos que en el ejercicio anterior, para resultados finales e intermedios respectivamente.
Sección 3 · Paso 1: reclasificar el suelo por tipo de arcilla
# Clase arcilla
alg_params = {
'DATA_TYPE': 5, # Float32
'INPUT_RASTER': parameters['suelo'],
'NODATA_FOR_MISSING': False,
'NO_DATA': -9999,
'RANGE_BOUNDARIES': 1, # min <= valor < max
'RASTER_BAND': 1,
'TABLE': ['0','2','0','2','3','1','3','99','0'],
'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
}
outputs['ClaseArcilla'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
feedback.setCurrentStep(1)
if feedback.isCanceled():
return {}
'DATA_TYPE': 5: en este caso se usa "Float32" (número decimal), a diferencia del "Int32" del ejercicio 1.'INPUT_RASTER': parameters['suelo']: usa directamente la capa de suelo que el usuario proporcionó (no un resultado intermedio, porque es el primer paso).'TABLE': ['0','2','0','2','3','1','3','99','0']: reclasifica en 3 rangos: 0-2 → clase 0, 2-3 → clase 1 (suelo arcilloso apto), 3-99 → clase 0.'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT: resultado intermedio, se usará en el paso 3.outputs['ClaseArcilla'] = processing.run(...): ejecuta la reclasificación y guarda el resultado bajo la llave'ClaseArcilla'.feedback.setCurrentStep(1)/if feedback.isCanceled(): return {}: igual que en el ejercicio 1, avanza la barra de progreso y permite cancelar la ejecución.
Sección 4 · Paso 2: reclasificar la zona inundable
# Clase zona inundable
alg_params = {
'DATA_TYPE': 5, # Float32
'INPUT_RASTER': parameters['elevacin'],
'NODATA_FOR_MISSING': False,
'NO_DATA': -9999,
'RANGE_BOUNDARIES': 1, # min <= valor < max
'RASTER_BAND': 1,
'TABLE': ['0','11','1','11','999','0'],
'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
}
outputs['ClaseZonaInundable'] = processing.run('native:reclassifybytable', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
feedback.setCurrentStep(2)
if feedback.isCanceled():
return {}
'INPUT_RASTER': parameters['elevacin']: esta vez la entrada es la capa de elevación proporcionada por el usuario.'TABLE': ['0','11','1','11','999','0']: valores de elevación entre 0 y 11 metros se marcan como clase 1 (zona inundable); el resto, como clase 0.outputs['ClaseZonaInundable'] = processing.run(...): guarda este segundo resultado intermedio bajo su propia llave.feedback.setCurrentStep(2): marca que ya se completaron 2 de los 3 pasos totales.
Sección 5 · Paso 3: combinar ambas condiciones con "AND"
# Zonas sorgo
alg_params = {
'DATA_TYPE': 3, # Int32
'INPUT': [outputs['ClaseArcilla']['OUTPUT'],outputs['ClaseZonaInundable']['OUTPUT']],
'NODATA_AS_FALSE': False,
'NO_DATA': -9999,
'REF_LAYER': outputs['ClaseZonaInundable']['OUTPUT'],
'OUTPUT': parameters['ZonasSorgo']
}
outputs['ZonasSorgo'] = processing.run('native:rasterbooleanand', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
results['ZonasSorgo'] = outputs['ZonasSorgo']['OUTPUT']
return results
'INPUT': [outputs['ClaseArcilla']['OUTPUT'], outputs['ClaseZonaInundable']['OUTPUT']]: en vez de una sola entrada, esta herramienta recibe una lista con las dos capas intermedias que se combinarán.'NODATA_AS_FALSE': False: los píxeles sin datos NO se tratarán automáticamente como si valieran "falso" (0).'REF_LAYER': outputs['ClaseZonaInundable']['OUTPUT']: define qué capa se usa como referencia de extensión y resolución para el resultado.'OUTPUT': parameters['ZonasSorgo']: aquí sí se guarda en el destino final, ya que es el último paso.outputs['ZonasSorgo'] = processing.run('native:rasterbooleanand', ...): ejecuta la operación lógica "Y" entre ambas capas reclasificadas.results['ZonasSorgo'] = outputs['ZonasSorgo']['OUTPUT']yreturn results: entrega el resultado final a QGIS, igual que en el ejercicio 1.
Nota importante: como la capa ClaseZonaInundable marca
con 1 las zonas inundables (justo lo contrario de lo que normalmente
buscaríamos para sembrar), conviene revisar con cuidado el significado real
de cada reclasificación al leer un modelo como este: el nombre de la variable
no siempre aclara si el valor 1 significa "cumple" o "no cumple" la condición
deseada.
Sección 6 · Métodos de identificación de la herramienta
def name(self) -> str:
return 'Zonas Sorgo'
def displayName(self) -> str:
return 'Zonas Sorgo'
def group(self) -> str:
return 'PASIG II'
def groupId(self) -> str:
return 'PASIG II'
def createInstance(self):
return self.__class__()
Estos 5 métodos cumplen exactamente la misma función que en el ejercicio 1:
identificar la herramienta ante QGIS (name,
displayName), ubicarla en un grupo del menú
(group, groupId) y
permitir que QGIS cree nuevas instancias de la clase cuando se ejecuta
(createInstance).