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

Ejercicio 4: NDVI con imágenes Landsat 8 o 9

Esta herramienta calcula el NDVI (Índice de Vegetación de Diferencia Normalizada) a partir de dos bandas de una imagen Landsat 8 o 9, usando la calculadora ráster de QGIS de forma automática desde código.

NDVI = (Banda 5 - Banda 4) / (Banda 5 + Banda 4)

Donde la Banda 4 es la reflectancia en el rojo (Red) y la Banda 5 es la reflectancia en el infrarrojo cercano (NIR). Este índice permite estimar qué tan densa y saludable está la vegetación en una zona.

Conceptos clave de POO y PyQGIS usados en el ejercicio

  • Docstring de clase: a diferencia de los ejercicios 1 a 3, aquí la clase Landsat_NDVI incluye un comentario de documentación (docstring) justo debajo de su declaración, listando los 8 métodos que la componen. Es una buena práctica de POO: documentar el "contrato" que cumple la clase antes de implementarlo.
  • Docstrings en cada método: cada método (name, displayName, etc.) tiene su propio comentario explicando qué debe devolver, siguiendo el formato estándar que usa QGIS en las plantillas oficiales de sus herramientas de procesamiento.
  • Método nuevo: shortHelpString: este método no aparecía en los ejercicios 1 a 3. Sirve para mostrar un texto de ayuda dentro de la propia ventana de la herramienta en QGIS, explicando al usuario qué hace y cómo usarla.
  • Cadenas con formato (f-strings): f'("{parameters["b5"]}@1" - ...)' es una cadena de texto especial (comienza con f) que permite insertar el valor de una variable directamente dentro del texto, usando llaves { }.
  • Ejecutar la calculadora ráster desde código: processing.run('qgis:rastercalculator', ...) arma una expresión matemática como texto y se la entrega a la herramienta "Calculadora ráster" de QGIS, en vez de tener que escribir manualmente la fórmula dentro de la interfaz gráfica.

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

04_toolscript_ndvi.py
"""
***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************

Script para una herramienta de cálculo del NDVI a partir de imágenes Landsat 8 o 9
La fórmula de cálculo es:

NDVI = (Banda 5 - Banda 4) / (Banda 5 + Banda 4)

Por lo tanto, se ocupan los archivos de las bandas que aparecen en la fórmula anterior.

"""

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

class Landsat_NDVI(QgsProcessingAlgorithm):
    """
    Métodos básicos de la herramienta:
    
    1. Nombre de la la herramienta (name).
    2. Nombre a mostyrar 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)
    """
    
    def name(self) -> str:
        """
        Returns the algorithm name, used for identifying the algorithm. This
        string should be fixed for the algorithm, and must not be localised.
        The name should be unique within each provider. Names should contain
        lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return "landsat_ndvi"

    def displayName(self) -> str:
        """
        Returns the translated algorithm name, which should be used for any
        user-visible display of the algorithm name.
        """
        return "NDVI con imágenes Landsat 8 o 9"

    def group(self) -> str:
        """
        Returns the name of the group this algorithm belongs to. This string
        should be localised.
        """
        return "PASIG II"

    def groupId(self) -> str:
        """
        Returns the unique ID of the group this algorithm belongs to. This
        string should be fixed for the algorithm, and must not be localised.
        The group id should be unique within each provider. Group id should
        contain lowercase alphanumeric characters only and no spaces or other
        formatting characters.
        """
        return "pasig_II"

    def shortHelpString(self) -> str:
        """
        Returns a localised short helper string for the algorithm. This string
        should provide a basic description about what the algorithm does and the
        parameters and outputs associated with it.
        """
        descripcion = """
            La herramienta calcula el NDVI (Normalized Difference Vegetation Index).
            NDVI es usado para cuantificar la humedad en la vegetación y es útil para
            comprender la densidad de la vegetación y evaluar cambios en la salud de
            las plantas.
            Esta herramienta funciona con imágenes de Landsat 8 o 9, y usa la siguiente
            ecuación:
            
            NDVI = (Band 5 – Band 4) / (Band 5 + Band 4)
            
            Donde:
            Band 4 es la banda 4, Rojo (Red)
            Band 5 es la banda 5, Infrarrojo cercano (Near Infrared o NIR)
        """
        return descripcion

    def initAlgorithm(self, config=None):
        """
        Here we define the inputs and output of the algorithm, along
        with some other properties.
        """
        #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 NDVI
        self.addParameter(QgsProcessingParameterRasterDestination('ndvi', 'Capa ráster con el NDVI', createByDefault=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, model_feedback):
        """
        Here is where the processing itself takes place.
        """
        feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
        outputs = {}
        results = {}
        print(parameters)
        #Parametros del algoritmo (calculadora ráster)
        alg_params = {'EXPRESSION':f'("{parameters["b5"]}@1" - "{parameters["b4"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b4"]}@1")',
                      'LAYERS':[parameters["b4"], parameters["b5"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["ndvi"]}
        #Ejecución del algoritmo (calculadora ráster)
        outputs['ndvi'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        print(outputs)
        results['ndvi'] = outputs['ndvi']['OUTPUT']
        print(results)
        return results
        
    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: es la clase base de la que debe heredar cualquier herramienta de procesamiento personalizada. Sin esta herencia, QGIS no reconocería nuestra clase como una herramienta ejecutable.
  • QgsProcessingParameterRasterLayer: se usa para declarar, dentro de initAlgorithm, cada parámetro de entrada que el usuario debe llenar con una capa ráster (en este ejercicio, las bandas 4 y 5).
  • QgsProcessingParameterRasterDestination: se usa para declarar el archivo ráster de salida (aquí, la capa con el NDVI calculado).
  • QgsProcessingMultiStepFeedback: permite reportar el progreso de la ejecución al usuario y detectar si canceló el proceso, aunque en este ejercicio solo haya un paso.
  • import processing: a diferencia de los ejercicios anteriores (que usaban from qgis import processing), aquí se importa el módulo processing directamente. Ambas formas permiten usar processing.run(...) para ejecutar cualquier herramienta de la Caja de herramientas desde código.

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

La clase Landsat_NDVI incluye un docstring que resume sus 8 métodos. A continuación se explica el propósito de cada uno:

  • name(): devuelve el identificador interno único de la herramienta ("landsat_ndvi"), usado internamente por QGIS.
  • displayName(): devuelve el nombre visible para el usuario en la interfaz de QGIS ("NDVI con imágenes Landsat 8 o 9").
  • group(): indica en qué grupo de la Caja de herramientas aparecerá esta herramienta ("PASIG II").
  • groupId(): es el identificador interno de ese mismo grupo ("pasig_II"), usado por QGIS para relacionar herramientas del mismo grupo.
  • shortHelpString(): devuelve un texto explicativo que QGIS muestra dentro de la ventana de la herramienta, describiendo qué hace el NDVI y qué representan sus bandas de entrada.
  • initAlgorithm(): declara los parámetros de entrada (bandas 4 y 5) y de salida (capa NDVI) que el usuario debe proporcionar antes de ejecutar la herramienta.
  • processAlgorithm(): contiene la lógica real del cálculo: arma la expresión matemática del NDVI y la ejecuta con la calculadora ráster de QGIS.
  • createInstance(): le indica a QGIS cómo crear una nueva copia de esta herramienta cada vez que se ejecuta.

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 NDVI
        self.addParameter(QgsProcessingParameterRasterDestination('ndvi', 'Capa ráster con el NDVI', createByDefault=True, defaultValue=None))
  • self.addParameter(QgsProcessingParameterRasterLayer('b4', ...)): declara la banda 4 (rojo) como parámetro de entrada, identificada internamente con la llave 'b4'.
  • self.addParameter(QgsProcessingParameterRasterLayer('b5', ...)): declara la banda 5 (infrarrojo cercano) como segundo parámetro de entrada.
  • self.addParameter(QgsProcessingParameterRasterDestination('ndvi', ...)): declara el archivo de salida donde se guardará la capa NDVI calculada.

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

    def processAlgorithm(self, parameters, context, model_feedback):
        feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
        outputs = {}
        results = {}
        print(parameters)
        #Parametros del algoritmo (calculadora ráster)
        alg_params = {'EXPRESSION':f'("{parameters["b5"]}@1" - "{parameters["b4"]}@1") / ("{parameters["b5"]}@1" + "{parameters["b4"]}@1")',
                      'LAYERS':[parameters["b4"], parameters["b5"]],
                      'CELLSIZE': 0,
                      'EXTENT':None,
                      'CRS':None,
                      'OUTPUT':parameters["ndvi"]}
        #Ejecución del algoritmo (calculadora ráster)
        outputs['ndvi'] = processing.run('qgis:rastercalculator', alg_params, context=context, feedback=feedback)
        print(outputs)
        results['ndvi'] = outputs['ndvi']['OUTPUT']
        print(results)
        return results
  • feedback = QgsProcessingMultiStepFeedback(1, model_feedback): aquí solo se declara 1 paso, porque todo el cálculo ocurre en una sola llamada a la calculadora ráster.
  • outputs = {} / results = {}: los mismos diccionarios vacíos vistos en ejercicios anteriores, para resultados intermedios y finales.
  • print(parameters): imprime en la consola de Python de QGIS el contenido de parameters, útil para depurar (revisar qué valores llegaron) mientras se construye la herramienta.
  • 'EXPRESSION': f'("{parameters["b5"]}@1" - "{parameters["b4"]}@1") / (...)': arma, como texto, la fórmula del NDVI. El símbolo @1 indica "la banda número 1 de esa capa" (el formato que espera la calculadora ráster de QGIS).
  • 'LAYERS': [parameters["b4"], parameters["b5"]]: lista de capas que participan en la expresión, para que la calculadora ráster sepa a cuáles hace referencia la fórmula.
  • 'CELLSIZE': 0: al dejarlo en 0, QGIS calcula automáticamente el tamaño de celda adecuado a partir de las capas de entrada.
  • 'EXTENT': None / 'CRS': None: al dejarlos en None, QGIS usa automáticamente la extensión y el sistema de coordenadas de las capas de entrada.
  • 'OUTPUT': parameters["ndvi"]: el resultado se guarda directamente en el destino final, ya que es el único paso del modelo.
  • outputs['ndvi'] = processing.run('qgis:rastercalculator', alg_params, ...): ejecuta la herramienta nativa "Calculadora ráster" con la expresión armada.
  • results['ndvi'] = outputs['ndvi']['OUTPUT'] y return results: entrega el resultado final a QGIS.
Pon a prueba lo analizado
Volver a PASIG II