PyQGIS · Material de apoyo para la clase de Programación en Ambientes de SIG (PASIG)

Ejercicio 5: SAVI con imágenes Landsat 8 o 9

Esta herramienta calcula el SAVI (Índice de Vegetación Ajustado al Suelo), una variante del NDVI que reduce la influencia del suelo desnudo cuando la cobertura vegetal es escasa.

SAVI = ((Banda 5 - Banda 4) / (Banda 5 + Banda 4 + 0.5)) * 1.5

Al igual que en el NDVI, la Banda 4 es el rojo (Red) y la Banda 5 es el infrarrojo cercano (NIR). La constante 0.5 es el "factor de ajuste de suelo" y 1.5 es un factor de escala.

Conceptos clave de POO y PyQGIS usados en el ejercicio

  • Misma estructura que el ejercicio 4: la clase landsat_savi repite exactamente el mismo patrón de 8 métodos que Landsat_NDVI. Esto demuestra una idea central de la POO: una vez que se domina la "plantilla" de una herramienta de procesamiento, se puede reutilizar para crear muchas herramientas distintas cambiando solo la fórmula y los nombres.
  • Comentarios numerados: a diferencia del ejercicio 4, aquí cada método tiene un comentario corto con su número (#1. Nombre de la herramienta, etc.) en vez de un docstring largo, mostrando otra forma válida de documentar el mismo tipo de información.
  • Convención de nombres en minúsculas: la clase se llama landsat_savi (todo en minúsculas), a diferencia de Landsat_NDVI o ClasesPendiente. Python permite ambos estilos, pero lo recomendado (PEP 8) es usar PascalCase para nombres de clase; este ejercicio es un buen ejemplo para notar esa inconsistencia al leer código real.
  • Grupo distinto: group() devuelve 'Landsat' en vez de 'PASIG II', mostrando que el agrupamiento de herramientas en la Caja de herramientas es una decisión libre de quien programa la herramienta.

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

05_toolscript_savi.py
"""
Herramienta de procesamiento para el calculo del índice SAVI

SAVI = ((Band 5 – Band 4) / (Band 5 + Band 4 + 0.5)) * (1.5)


"""
#Se importan las bibliotecas o módulos básicos
from qgis.core import QgsProcessingAlgorithm #Clase de la que se hereda en nuestra herramienta
from qgis.core import QgsProcessingParameterRasterLayer #Para cargar capas raster al diccionario de parámetros
from qgis.core import QgsProcessingParameterRasterDestination #Para guardar una salida ráster
from qgis.core import QgsProcessingMultiStepFeedback #Para mostrar el progreso de la operación
import processing #Para usar en "processing.run()"

#Se crea la clase para la herramienta
class landsat_savi(QgsProcessingAlgorithm):
    """
    Métodos básicos de la herramienta:
    
    1. Nombre de la la herramienta (name).
    2. Nombre a mostrar en la GUI (displayName).
    3. Grupo de herramientas al que pertenece la herramienta actual (group)
    4. ID del grupo al que pertence la herramienta (groupId)
    5. Ayuda corta de la herramienta (shortHelpString)
    6. Cargar entradas como parámetros de algorítmo (initAlgorithm)
    7. Algoritmo de procesamiento (processAlgorithm)
    8. Creación de la instancia (createInstance)
    """
    #1. Nombre de la herramienta
    def name(self):
        return 'landsat_savi'
    
    #2. Nombre a mostrar en la GUI
    def displayName(self):
        return 'SAVI con imágenes Landsat 8 o 9'
    
    #3. Grupo de herramientas al que pertenece la herramienta actual
    def group(self):
        return 'Landsat'
    
    #4. ID del grupo al que pertenece la herramienta
    def groupId(self):
        return 'landsat'
        
    #5. Ayuda corta de la herramienta
    def shortHelpString(self):
        descripcion = """
            Aquí va a ir la traducción de la página del USGS para el caso de SAVI
        """
        return descripcion
    
    #6. Cargar entradas como parámetros de algoritmo
    def initAlgorithm(self, config=None):
        #Capa de entrada con la banda 4
        self.addParameter(QgsProcessingParameterRasterLayer('b4', 'Banda 4 de Landsat 8 o 9', defaultValue=None))
        #Capa de entrada con la banda 5
        self.addParameter(QgsProcessingParameterRasterLayer('b5', 'Banda 5 de Landsat 8 o 9', defaultValue=None))
        #Capa de salida con el SAVI
        self.addParameter(QgsProcessingParameterRasterDestination('savi', 'Capa ráster con el SAVI', createByDefault=True, defaultValue=None))

    #7. Algoritmo de procesamiento
    def processAlgorithm(self, parameters, context, model_feedback):
        outputs = {}
        results = {}
        feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
        #Parametros del algoritmo (calculadora ráster)
        alg_params = {'EXPRESSION':f'(("{parameters["b5"]}@1" - "{parameters["b4"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b4"]}@1" + 0.5)) * 1.5',
                      'LAYERS':[parameters["b4"], parameters["b5"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["savi"]}
        outputs['savi'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        results['savi'] = outputs['savi']['OUTPUT']
        return results
    
    #8. Creación de la instancia
    def createInstance(self):
        return self.__class__()

Sección 1 · Para qué sirve cada biblioteca importada

from qgis.core import QgsProcessingAlgorithm #Clase de la que se hereda en nuestra herramienta
from qgis.core import QgsProcessingParameterRasterLayer #Para cargar capas raster al diccionario de parámetros
from qgis.core import QgsProcessingParameterRasterDestination #Para guardar una salida ráster
from qgis.core import QgsProcessingMultiStepFeedback #Para mostrar el progreso de la operación
import processing #Para usar en "processing.run()"
  • QgsProcessingAlgorithm: clase base necesaria para que landsat_savi sea reconocida por QGIS como una herramienta de procesamiento (exactamente el mismo propósito que en el ejercicio 4).
  • QgsProcessingParameterRasterLayer: permite declarar las bandas 4 y 5 como capas ráster de entrada.
  • QgsProcessingParameterRasterDestination: permite declarar la capa de salida donde se guardará el SAVI calculado.
  • QgsProcessingMultiStepFeedback: gestiona el progreso y la posibilidad de cancelar, aunque aquí también sea un único paso.
  • import processing: da acceso a processing.run(...) para ejecutar la calculadora ráster desde el propio script.

Sección 2 · Para qué sirve cada método de la clase

  • name(): devuelve 'landsat_savi', el identificador interno de la herramienta.
  • displayName(): devuelve el nombre visible 'SAVI con imágenes Landsat 8 o 9'.
  • group(): ubica la herramienta en el grupo 'Landsat' del menú de procesos.
  • groupId(): identificador interno de ese grupo ('landsat').
  • shortHelpString(): texto de ayuda mostrado en la ventana de la herramienta (en este caso, un texto pendiente de completar por quien programó el ejercicio).
  • initAlgorithm(): declara las entradas (bandas 4 y 5) y la salida (capa SAVI).
  • processAlgorithm(): arma la fórmula del SAVI y la ejecuta con la calculadora ráster.
  • createInstance(): permite a QGIS crear nuevas copias de la herramienta.

Sección 3 · Declaración de parámetros (initAlgorithm)

    def initAlgorithm(self, config=None):
        #Capa de entrada con la banda 4
        self.addParameter(QgsProcessingParameterRasterLayer('b4', 'Banda 4 de Landsat 8 o 9', defaultValue=None))
        #Capa de entrada con la banda 5
        self.addParameter(QgsProcessingParameterRasterLayer('b5', 'Banda 5 de Landsat 8 o 9', defaultValue=None))
        #Capa de salida con el SAVI
        self.addParameter(QgsProcessingParameterRasterDestination('savi', 'Capa ráster con el SAVI', createByDefault=True, defaultValue=None))

Idéntico en estructura al ejercicio 4: dos capas de entrada ('b4', 'b5') y una capa de salida ('savi').

Sección 4 · Cálculo del SAVI (processAlgorithm)

    def processAlgorithm(self, parameters, context, model_feedback):
        outputs = {}
        results = {}
        feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
        #Parametros del algoritmo (calculadora ráster)
        alg_params = {'EXPRESSION':f'(("{parameters["b5"]}@1" - "{parameters["b4"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b4"]}@1" + 0.5)) * 1.5',
                      'LAYERS':[parameters["b4"], parameters["b5"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["savi"]}
        outputs['savi'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        results['savi'] = outputs['savi']['OUTPUT']
        return results
  • 'EXPRESSION': f'(("{parameters["b5"]}@1" - "{parameters["b4"]}@1") / (... + 0.5)) * 1.5': arma la fórmula completa del SAVI, incluyendo el factor de ajuste de suelo (0.5) y el factor de escala (1.5), tal como se explicó al inicio de esta página.
  • 'LAYERS', 'CELLSIZE', 'EXTENT', 'CRS': cumplen exactamente el mismo propósito que en el ejercicio 4 (definir qué capas participan y dejar que QGIS calcule automáticamente el tamaño de celda, la extensión y el sistema de coordenadas).
  • outputs['savi'] = processing.run('qgis:rastercalculator', ...): ejecuta la calculadora ráster con la fórmula del SAVI.
  • results['savi'] = outputs['savi']['OUTPUT'] y return results: entrega el resultado final a QGIS.

Nota cómo, salvo por la fórmula matemática y los nombres de las llaves ('ndvi' vs. 'savi'), este método es prácticamente idéntico al del ejercicio 4: es un ejemplo claro de cómo, una vez comprendida la lógica de un patrón de código, se puede adaptar fácilmente para calcular otros índices espectrales.

Pon a prueba lo analizado
Volver a PASIG II