Crear herramientas en QGIS

Roberer

Por Roberto Jiménez

Geospatial & GIS Analyst

Índice


Las herramientas de procesamiento

Las herramientas son el alma de un SIG puesto que son los algoritmos que nos permiten tratar la información geográfica y generar nuevos datos.

QGIS trae por defecto numerosas herramientas de geoproceso dentro del módulo Processing, y también permite añadir herramientas de terceros, ampliando las características y posibilidades del programa. Pero es que además nos da la oportunidad de crear nuevas herramientas a nuestra medida para adaptar QGIS a nuestro flujo de trabajo.

Las herramientas en QGIS se encuentran agrupados en proveedores dentro de la caja de herramientas de Procesos:


Caja geoprocesos QGIS

Cuando decimos aquí herramientas no nos referimos a plugins, que son extensiones de QGIS que modifican el propio programa y pueden abarcar aspectos más profundos como la interfaz gráfica. Nos referimos a los algoritmos de procesamiento del módulo Processing que cuentan una interfaz sencilla similar a la mostrada arriba y que podremos crearlos y añadirlos a la caja de herramientas.

Visto de otro modo, un plugin es un mini-programa en forma de carpeta que contiene una serie de archivos y subcarpetas, entre ellos el script (comúnmente en Python pero también admite otros lenguajes), mientras que una herramienta es un script que dentro del módulo Processing QGIS toma una sencilla interfaz que permite definir los parámetros a ejecutar.

El módulo processing es una característica fundamental de QGIS que proporciona un entorno de trabajo para trabajar con los distintos algoritmos de geoprocesamiento que pertenecen al propio proyecto QGIS, pero que además incorpora las herramientas que otros proveedores externos como GDAL o GRASS desarrollan para la plataforma.


Módulo Processing QGIS

Incluso es posible añadir otras aplicaciones externas a través de los plugins de QGIS como LASTools o R. El caso es que este módulo se encuentra programado en Python, y como tal podremos acceder a él a través de la consola de Python de QGIS que podemos encontrar en la misma caja de procesos.

Además, también permite crear modelos de forma similar a lo que hace Model Builder en ArcGIS de forma gráfica y crear con ellos ventanas interactivas o exportarlos como scripts de Python.

En este enlace tenéis la documentación oficial del módulo Processing en español.

Estructura de las herramientas

La caja de herramientas de Procesos posee un icono de Python que desplega un menú con varias opciones. Una de ellas nos permite generar un sencillo script a modo de plantilla con comentarios sobre cada una de las partes que debe poseer el código para que la herramienta funcione correctamente.


Crear scripts QGIS

Pero también nos permite crear scripts de 0, abrir alguno existente que tengamos en nuestro PC o incorporarlo a la caja de herramientas para tenerlo a mano.

Al hacer clic sobre la opciónd e la plantilla se abrirá el editor de scripts de procesamiento con un sencillo script (que consiste en una herramienta que copia el archivo vectorial que seleccionemos) en el que podemos diferenciar dos grandes partes:

⚠ El editor de scripts de procesamiento NO es lo mismo que la consola de Python de QGIS.

  1. Codificación del sistema e importación de módulos
  2. Definición de una nueva clase

El primer punto es algo básico en cualquier script de Python y no iba a faltar aquí. El tema principal está en que el diseño de la herramienta consistirá en crear una nueva clase.

Al construir una nueva clase en Python lo que se hace es crear un tipo de objeto nuevo al que podremos aplicarle métodos. ¿Cuáles? Podemos crearlos desde 0 o heredar todas aquellas que ya pertenecen a otra clase.

QgsProcessingAlgorithm es la clase en la que se encuentran construidas las herramientas de la caja de geoprocesos de QGIS. Dicho de otra forma, cada una de estas herramientas es una clase construida sobre la clase QgsProcessingAlgorithm.

Usando los métodos de QgsProcessingAlgorithm a través del concepto de herencia podremos definir los parámetros de entrada, los geoprocesos a realizar y los parámetros de salida de una herramienta processing, así como la información que se ofrezca en pantalla, todo una ventana con un aspecto similar a la de cualquier herramienta de la Caja de Procesos..

De forma esquemática y por orden, en nuestra clase o herramienta nueva debemos definir:

  1. El nombre de la nueva clase y la clase de la que heredará los métodos y variables QgsProcessingAlgorithm
  2. Los nombres de todos los parámetros (atributos) que se van a usar en la herramienta
  3. La informaciónbásica de la herramienta como su nombre, descripción, enlace de ayuda…
  4. Las propiedades de todos los parámetros de entrada y salida del algoritmo
  5. Las funciones de geoproceso que tomarán los parámetros definidos previamente

Estructura herramienta geoproceso QGIS

Crear una herramienta

A continuación veremos punto por punto y a modo de ejemplo el proceso para construir esta herramienta que aplica el algoritmo Diferencia a todos los shapefiles de una carpeta.

1- Codificación del sistema e importación de módulos

En esta primera parte la plantilla nos lo pone fácil ofreciéndonos ya la importación básica de módulos necesaria y la codificación del sistema. En caso de necesitarlo será conveniente añadir más módulos o cambiar la codificación si nos da algún problema.

Para nuestro ejemplo elegiremos la codificación utf-8 e importaremos los siguientes módulos:

# -*- coding: utf-8 -*-

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFile,
                       QgsProcessingParameterFolderDestination)
from qgis import processing
import os

2- Comenzar a definir la nueva clase

Para definir la nueva clase tendremos que seguir la siguiente estructura:

class Multidifference(QgsProcessingAlgorithm):
      ## definición del resto de elementos 

Con class indicamos que estamos creando una nueva clase, y tras el nombre que queramos darle (Multidifference) debemos indicar entre paréntesis el nombre de la clase de la que heredará los métodos y subclases (crearlos de 0 es algo muy avanzado y la gracia de Python es ahorrar tiempo con estas cosas), en este caso QgsProcessingAlgorithm.

3- Nombrar los parámetros de la herramienta

A continuación, lo primero será crear las variables de clase de nuestra herramienta en forma de objetos que almacenen como texto los nombres de los distintos parámetros que necesitará nuestra herramienta para funcionar:

class Multidifference(QgsProcessingAlgorithm):
      INPUT = 'INPUT'
      OVERLAY = 'OVERLAY'
      OUTPUT = 'OUTPUT'    

Más adelante configuraremos con Init.Algorithm el INPUT (las capas vectoriales que se encuentren en una carpeta), el OVERLAY (la capa que se superpondrá al resto) y el OUTPUT (la carpeta de salida junto con el nombre y la extensión de los nuevos archivos).

4- Información de la herramienta

Lo siguiente es darle forma a la herramienta definiendo los siguientes métodos, cada uno correspondiendo a un ítem distinto de la herramienta, algunos correspondientes a la ventana gráfica y otros a su propia estructura interna:

    def __init__(self):   ## Lo primero que debemos definir. Es el constructor de la clase (imprescindible)
        super().__init__() 
 
    def name(self):   ## Nombre identificativo de la herramienta (solo números y minúsculas)         
        return "recortemultiple" 
 
    def tr(self, text):   ## Método para que el texto de la interfaz se traduzca (si es posible)        
        return QCoreApplication.translate("Multidifference", text) 
 
    def displayName(self):   ## Nombre que se mostrará al usuario en la interfaz         
        return self.tr("Diferencia múltiple") 
        
    def group(self):   ## Nombre del grupo que contendrá la herramienta en la caja de procesos         
        return self.tr("Scripts de prueba") 

    def groupId(self):   ## Nombre identificativo del grupo (solo números y minúsculas)
        return "scriptsdeprueba"
    
    def shortHelpString(self):   ## Texto descriptivo con las instrucciones de la herramienta         
        return self.tr("Este proceso recorta todos los shapes de un directorio especificando una única capa") 
    
    def helpUrl(self):   ## Enlace que se abrirá al hacer clic sobre el botón de ayuda         
        return "https://programapa.wordpress.com" 
 
    def createInstance(self):   ## Crea una nueva instancia de la clase que estamos creando (imprescindible)          
        return type(self)() 

¿Qué es self? Es el objeto o instancia que referencia a la clase para poder aplicarle los métodos que estamos definiendo. En este caso, self se refiere nuestra clase Multidifference.

¿Qué es __init__? Es el constructor del ‘patrón de diseño del objeto’ o constructor de clase (object state), es decir, el que organiza la estructura de la herramienta que luego vemos representada en su ventana o salida gráfica. Al usar super()__init__() tomamos la estructura que viene marcada en la clase QgsProcesingAlgorithm de la que heredamos los métodos que hemos definido.

Gracias a esto, cuando definimos los métodos name, helpUrl, etc. lo que hacemos es simplemente darles un valor que luego se nos mostrará en su lugar correspondiente en la ventana.

5- Definición de los parámetros

Después hay que definir los parámetros indicados al comienzo de la creación de la clase: las capas de entrada INPUT, la capa superposición OVERLAY y las capas de salida OUTPUT.

La subclase que se utiliza para esta cuestión es initAlgorithm(), y dentro de ella hay que usar el método addParameter() para irlos añadiendo uno a uno usando funciones QgsProcessingParameter. Estas funciones crearán objetos de tipo QVariant y estarán referenciados por las cadenas de texto que definimos al principio para nombrar a los parámetros.

## iniciamos la definicion de los parametros
def initAlgorithm(self, config=None): 
        
        ## capeta con los archivos de entrada
        self.addParameter(QgsProcessingParameterFile(
            self.INPUT, 
            self.tr("Input folder (Required)"),
            behavior = QgsProcessingParameterFile.Folder))
         
        ## capa de tipo poligono que hace de overlay   
        self.addParameter(QgsProcessingParameterFeatureSource(            
            self.OVERLAY,             
            self.tr("Input overlay layer (Required)"), 
            [QgsProcessing.TypeVectorPolygon])) 
        
        ## carpeta de salida de los archivos generados    
        self.addParameter(QgsProcessingParameterFolderDestination(
            self.OUTPUT, 
            self.tr("Output folder (Required)"))) 

Estos y muchos otros parámetros los podéis encontrar en la documentación oficial.

6- Definición del algoritmo

La última parte de nuestra clase corresponde a todos aquellos procesos que se ejecutan al pulsar el botón ejecutar de nuestra herramienta. El método a utilizar para este apartado es processAlgorithm:

def processAlgorithm(self, parameters, context, feedback):

      ## Se recogerán las rutas de entrada y salida introducidas por el usuario de la herramienta en cadenas de texto
      ruta_entrada = self.parameterAsString(parameters, self.INPUT, context)
      ruta_salida = self.parameterAsString(parameters, self.OUTPUT, context)

      ## Se listarán los archivos de la ruta de entrada
      archivos = os.listdir(ruta_entrada)

      ## Se iniciará un bucle que recorrerá todos los archivos de la lista y seleccionará solo los shapes
      for archivo in archivos:
            if os.path.splitext(archivo)[1] == '.shp':
                
                 nombre = os.path.splitext(archivo)[0]   ## el nombre de la capa
               
                 ## construcción de las rutas de entrada y salida 
                 capa_input = ruta_entrada + '\\' + archivo
                 capa_output = ruta_salida + '\\' + nombre + '_cut.shp'
                 
                 ## geoproceso 'difference' que se aplicará a cada shapefile
                 processing.run("qgis:difference", {'INPUT': entrada, 'OVERLAY': parameters['OVERLAY'], 'OUTPUT': salida})


Script ‘completado’: Multidifference

Y digo ‘completado’ porque realmente no estaría completo, ya que se le podría añadir multitud de mejoras: que asegurara su correcto funcionamiento en toda clase de circunstancias (se detiene cuando la geometría de una capa de entrada es incorrecta), que podamos elegir el sufijo con el que se guardarían las nuevas capas, que se creara una carpeta de resultados automáticamente dentro de la carpeta INPUT, mayor comunicación con el usuario sobre el progreso del proceso o reescribir los geoprocesos para que resultara más eficiente!

Sin embargo, creo que para ilustrar el proceso de creación de herramientas con lo expuesto es más que suficiente para no complicarlo más aún, y muchas de estas cuestiones pueden encontrarse en la documentación oficial sobre la creación de nuevas herramientas de geoproceso.

El script de este post al completo quedaría así:

# -*- coding: utf-8 -*-

from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (QgsProcessing,
                       QgsProcessingException,
                       QgsProcessingAlgorithm,
                       QgsProcessingParameterFeatureSource,
                       QgsProcessingParameterFile,
                       QgsProcessingParameterFolderDestination)
from qgis import processing
import os

class Multidifference(QgsProcessingAlgorithm):
    INPUT = 'INPUT'
    OVERLAY = 'OVERLAY'
    OUTPUT = 'OUTPUT'    
      
    def __init__(self):   ## Lo primero que debemos definir. Es el constructor de la clase (imprescindible)
        super().__init__() 
 
    def name(self):   ## Nombre identificativo de la herramienta (solo números y minúsculas)         
        return "recortemultiple" 
 
    def tr(self, text):   ## Método para que el texto de la interfaz se traduzca (si es posible)        
        return QCoreApplication.translate("Multidifference", text) 
 
    def displayName(self):   ## Nombre que se mostrará al usuario en la interfaz         
        return self.tr("Diferencia múltiple") 
        
    def group(self):   ## Nombre del grupo que contendrá la herramienta en la caja de procesos         
        return self.tr("Scripts de prueba") 

    def groupId(self):   ## Nombre identificativo del grupo (solo números y minúsculas)
        return "scriptsdeprueba"
    
    def shortHelpString(self):   ## Texto descriptivo con las instrucciones de la herramienta         
        return self.tr("Este proceso recorta todos los shapes de un directorio especificando una única capa") 
    
    def helpUrl(self):   ## Enlace que se abrirá al hacer clic sobre el botón de ayuda         
        return "https://programapa.wordpress.com" 
 
    def createInstance(self):   ## Crea una nueva instancia de la clase que estamos creando (imprescindible)          
        return type(self)() 
        
    ## iniciamos la definicion de los parametros
    def initAlgorithm(self, config=None): 
            
            ## capeta con los archivos de entrada
            self.addParameter(QgsProcessingParameterFile(
                self.INPUT, 
                self.tr("Input folder (Required)"),
                behavior = QgsProcessingParameterFile.Folder))
             
            ## capa de tipo poligono que hace de overlay   
            self.addParameter(QgsProcessingParameterFeatureSource(            
                self.OVERLAY,             
                self.tr("Input overlay layer (Required)"), 
                [QgsProcessing.TypeVectorPolygon])) 
            
            ## carpeta de salida de los archivos generados    
            self.addParameter(QgsProcessingParameterFolderDestination(
                self.OUTPUT, 
                self.tr("Output folder (Required)"))) 
                
    def processAlgorithm(self, parameters, context, feedback):

        ## Se recogerán las rutas de entrada y salida introducidas por el usuario de la herramienta en cadenas de texto
        ruta_entrada = self.parameterAsString(parameters, self.INPUT, context)
        ruta_salida = self.parameterAsString(parameters, self.OUTPUT, context)

        ## Se listarán los archivos de la ruta de entrada
        archivos = os.listdir(ruta_entrada)

        ## Se iniciará un bucle que recorrerá todos los archivos de la lista y seleccionará solo los shapes
        for archivo in archivos:
            if os.path.splitext(archivo)[1] == '.shp':
                    
                nombre = os.path.splitext(archivo)[0]   ## el nombre de la capa
                   
                ## construcción de las rutas de entrada y salida 
                capa_input = ruta_entrada + '\\' + archivo
                capa_output = ruta_salida + '\\' + nombre + '_cut.shp'
                     
                ## geoproceso 'difference' que se aplicará a cada shapefile
                processing.run("qgis:difference", {'INPUT': capa_input, 'OVERLAY': parameters['OVERLAY'], 'OUTPUT': capa_output})

Este y otros scripts ilustrativos de la programación con Sistemas de Información Geográfica podéis encontrarlos en mi GitHub

Enlaces de interés

Bibliografía que me ha resultado de utilidad para entender los conceptos de Python que han intervenido en la creación de esta herramienta: