Los plugins o complementos son extensiones que extienden la funcionalidad de QGIS más allá de las posibilidades que trae por defecto. De hecho, una de las cuestiones más importantes acerca de QGIS y su concepción como Sistema de Información Geográfica libre es precisamente que cualquiera con los conocimientos apropiados contribuya a aumentar sus funcionalidades a través de estos plugins.
QGIS cuenta con un completo repositorio de plugins al cual podemos acceder directamente desde el propio programa de escritorio, realizar búsquedas e instalarlos en un momento, así como compartir los que hayamos credo nosotros.
A diferencia de las herramientas de geoproceso, los plugins otorgan una mayor libertad para personalizar la ventana gráfica de la herramienta o la estructura de los algoritmos que queremos que se ejecuten. Hay de todo tipo: algunos funcionan prácticamente como herramientas de geoproceso, pero otros añaden maravillosas funcionalidades como la de mostrar nuestro lienzo de QGIS en GoogleEarth o tener a mano un catálogo de mapas base.
Para crear nuestros plugins necesitaremos tener listo lo siguiente:
La instalación de QGIS a través del paquete de OSGeo (OSGeo4W para Windows) es necesaria para la creación de plugins puesto que incluye la consola Shell necesaria para compilar el plugin y QtDesigner.
Podéis descargarlo desde aquí
Para programar en Python es recomendable en muchas ocasiones usar una IDE como Pycharm o VSCode para trabajar cómodamente, aunque también puede hacerse a través del editor de texto de la consola de Python de QGIS.
Este plugin nos permite crear automáticamente toda la estructura de archivos básica necesaria para construir el plugin. Bastará con instalarlo a través del administrador de complementos de QGIS e iniciarlo desde el propio QGIS para comenzar a crear el plugin.
Con este plugin podremos actualizar nuestro plugin para ver en tiempo real los cambios que hagamos durante su creación y probar si las características que implementamos dan los resultados esperados.
Este programa servirá para construir la salida gráfica de nuestro plugin a través de un lienzo en el que podremos colocar distintos objetos y enlazarlos con los objetos de nuestro código de Python.
QtDesigner puede descargarse o venir integrado con la instalación de QGIS a través del paquete de OSGeo4W. ¿No parece de QGIS verdad? Ocurre que QGIS está desarrollado en base a Qt, un framework que provee herramientas de desarrollo que también es utilizado por otras aplicaciones informáticas, y entre estas herramientas se encuentra QtDesigner.
Vamos a crear un plugin que realice un Multiparte a partes sencillas a todas las capas vectoriales que se encuentren en una carpeta, dado que la herramienta de procesamiento que proporciona QGIS solo permite aplicarlo a una capa cada vez que se ejecuta.
Este es un procedimiento básico en el control de calidad de datos geográficos y se le conoce en inglés como Exploding Multipart Polygons. A nuestro nuevo plugin lo llamaremos MultiExploding.
Para entender más a fondo lo que expongo a continuación recomiendo echarle un vistazo a los otros posts que tengo sobre Python y QGIS:
El primer paso será instalar Plugin Builder 3 en QGIS, activarlo e iniciarlo. Una vez hecho esto, se abrirá la siguiente ventana:
Debemos rellenar todos los datos que pide. Como podéis observar, se sugieren ejemplos para introducir correctamente los nombres de la nueva clase de Python que se va a crear, el módulo etc.
El siguiente paso será rellanar el About, que es la descripción detallada de lo que hace el plugin que aparecerá en el repositorio oficial en caso de que lo compartamos.
A continuación tendremos que escoger una plantilla para nuestro plugin, el menú en el que aparecerá el plugin cuando lo añadamos a QGIS y un texto descriptivo que aparecerá en dicho menú:
El siguiente paso es para pedir que se creen archivos adicionales que pueden ser de utilidad. Por defecto se encuentran marcados:
Por último, podemos añadir distintos enlaces para compartir el repositorio de código del plugin, la página web o las etiquetas que mejor describan nuestro plugin para que sea encontrado fácilmente. Además, podemos marcar el plugin como experimental para advertir que no está completo y/o puede generar resultados no deseados:
Una vez hecho todo esto, guardaremos el plugin en la ruta que queramos. En el directorio escogido se creará una nueva carpeta con el nombre del plugin en la que se encontrarán todos los archivos del plugin:
Si todo ha salido bien, se mostrará el siguiente mensaje:
Es muy probable que al crear el plugin haya saltado el siguiente error:
Significa que no pudo compilarse (en resumen, traducirse al lenguaje del PC para que funcione) y que tendremos que hacerlo manualmente, pero es muy sencillo. Debemos abrir la terminal – Shell – de OSGEO e introducir los siguientes comandos:
cd C:\ruta_carpeta_plugin call qt5_env.bat py3_env.bat pyrcc5 –o resources.py resources.qrc
Si todo ha salido bien, el proceso completo sería el siguiente:
La estructura de archivos dentro del plugin quedaría de la siguiente manera:
Con el plugin ya compilado, tan solo tendremos que copiarlo a la carpeta de plugins de QGIS y, si tenemos QGIS abierto, reiniciarlo.
En Windows, la carpeta de plugins de QGIS suele encontrarse en
C:\Users\Usuario\AppData\Roaming\QGIS\QGIS3\profiles\default\python\plugins
Una vez abierto de nuevo QGIS, recordad activar el plugin desde el administrador de complementos.
Para diseñar la interfaz tendremos que abrir QtDesigner y abrir con él un archivo que encontraremos dentro de la carpeta del plugin llamado nombreplugin_dialog_base.ui (en mi caso Multi_Exploding_dialog_base.ui) que es el que contiene los datos relativos a la interfaz de usuario del plugin.
La ventana que se abre dentro de Qt Designer será el lienzo en el que montaremos la interfaz de usuario de nuestro plugin, es decir, los botones para seleccionar la carpeta de entrada, los parámetros con los que queremos que se ejecute la herramienta, el nombre de la carpeta de salida…
La plantilla que hayamos seleccionado en la creación del plugin con Plugin Builder 3 determinará el aspecto que presentará nuestra interfaz. Al haber seleccionado Tool button with dialog obtendremos la clásica ventana con los botones de aceptar para ejecutar la herramienta y cancelar para cerrarla.
Lo siguiente que tendremos que hacer será arrastrar los widgets que necesitemos desde las cajas de la izquierda a nuestro lienzo. Para nuestro plugin necesitaremos:
Podemos cambiar tanto la posición de los widgets como su tamaño o la etiqueta que muestran:
En la parte derecha de Qt Designer nos aparecerán las propiedades de cada uno de estos widgets y la posibilidad de modificarlas (algunas de ellas). Las más básicas que necesitamos conocer para programar el plugin son:
En la documentación de las clases de los widgets que hemos incorporado podremos encontrar los métodos que nos permitirán jugar con ellos:
Una vez guardamos los cambios, si vamos a QGIS y recargamos el plugin usando Plugin Reloader (1) al volver a hacer click en nuestro plugin (2) se deberían ver los cambios hechos en la interfaz:
El último paso consiste en programar el script para darle funcionalidad a la interfaz y hacer que se ejecute el algoritmo de Multiparte a partes sencillas para todas las entidades de todos los archivos vectoriales de la carpeta de entrada, y que sus resultados se almacenen en la carpeta con el nombre indicado (que se localizará dentro de la carpeta de entrada)
Para ello, tendremos que abrir con un IDE (VSCode en las capturas) el archivo nombreplugin.py que encontraremos en la carpeta del plugin. En mi caso, dicho archivo es Multi_Exploding.py.
Al abrirlo veremos que la estructura básica del script se encuentra ya creada gracias a Model Builder 3. La estructura es muy similar a la que vemos en las herramientas de geoproceso:
La mayor diferencia respecto a las herramientas de geoproceso va a estar en cómo definiremos la nueva clase. En esta ocasión no usaremos la herencia para basar nuestro plugin en otra clase, y tampoco tendremos que definir los parámetros justo al comienzo de la clase.
Nos encontraremos los siguientes métodos ya definidos para nuestra nueva clase:
def __init__(self, iface) | Constructor de clase |
def tr(self, message) | Traductor de la interfaz |
def add_action(self, iconpath…) | Configuración del icono en la barra de herramientas de QGIS |
def initGui(self) | Integra el plugin en la interfaz de usuario de QGIS |
def unload(self) | Configura el proceso de eliminación del plugin de la interfaz de QGIS |
def run(self) | Código que se ejecutará una vez se abra el plugin |
Por lo general, no será necesario editar ninguno de estos métodos salvo run, que es el que ejecutará las funciones que implementemos al script.
Lo que sí tendremos que hacer es crear un nuevo método para dar funcionalidad a la interfaz de nuestro plugin, como veremos en el siguiente punto.
⚠ Self es el nombre que habitualmente se le da al objeto que representa a la propia clase. Iface es el nombre obligatorio para referenciar la interfaz de QGIS. Ambos quedaron definidos por Plugin Builder 3 en el método __init__ al comienzo de la clase.
Lo primero que debemos hacer es definir un nuevo método llamado select_input_folder para nuestra clase (self) por el cual, cuando se invoque, se acceda al sistema de archivos del PC y se copie nuestro Line Edit (nombrado por defecto lineEdit) la ruta de la carpeta seleccionada:
def select_input_folder(self): inputfolder = QFileDialog.getExistingDirectory(self.dlg,"Choose input directory", "",QFileDialog.DontResolveSymlinks) self.dlg.lineEdit.setText(inputfolder)
Dicho método lo hemos colocado justo después del método unload que ya venía definido. Lo que hace es guardar en el objeto inputfolder la ruta seleccionada y lo inserta con .setText() en el Line Edit.
Para que al pulsar el botón accedamos al sistema de archivos deberemos añadir la línea resaltada en la sentencia if que hay al comienzo del bloque run:
def run(self): """Run method that performs all the real work""" # Create the dialog with elements (after translation) and keep reference # Only create GUI ONCE in callback, so that it will only load when the plugin is started if self.first_start == True: self.first_start = False self.dlg = MultiExplodingDialog() self.dlg.pushButton.clicked.connect(self.select_input_folder)
Dentro de esta sentencia if añadiremos todas aquellas conexiones de que queremos que se establezcan al abrir el plugin dentro de QGIS. En este caso, lo que hacemos es indicar con los métodos .clicked.connect() que cuando hagamos click a nuestro botón de nombre pushButton se ejecute el método .select_input_folder que hemos definido anteriormente.
El último paso para que funcione será importar la clase QFileDialog al comienzo de nuestro script para poder navegar por el sistema de archivos:
from qgis.PyQt.QtWidgets import QAction, QFileDialog
Guardamos los cambios, recargamos el plugin con Plugin Reloader y probamos si funciona:
Tanto el primer Line Edit (lineEdit) como el segundo (lineEdit_2) por defecto almacenarán el texto que contengan aunque cerremos el plugin y volvamos a abrirlo. Para evitar esto, añadiremos las siguientes líneas dentro de run (después de la sentencia if que ya modificamos previamente) para que ambos Line Edits se limpien al iniciar el plugin:
self.dlg.lineEdit.setText('') self.dlg.lineEdit_2.setText('')
Es tan sencillo como aplicar el método .setText() a los Line Edits y darles un texto vacío.
Lo único que nos quedaría es indicar qué ocurre cuando pulsemos el botón de ACEPTAR de nuestro plugin. Básicamente, se tomará el texto de nuestros Line Edits como variables de entrada y salida del algoritmo Multiparte a partes sencillas que queremos aplicar a todos los shapefiles de la carpeta seleccionada.
Lo primero será importar los siguientes módulos:
from qgis.core import QgsVectorLayer, QgsField from qgis.utils import iface import processing import os import shutil
A continuación, nos iremos a la sentencia if result que se encuentra al final del script…
if result: # Do something useful here - delete the line containing pass and # substitute with your code. pass
… y tal como se indica sustituiremos su contenido por todo aquello que queramos que suceda al pulsar ACEPTAR (o ejecutar) Lo tenéis a detallado a continuación:
if result: ## objetos con las rutas de entrada y salida a partir de los Line Edits entrada = self.dlg.lineEdit.text() + '/' nombre_salida = self.dlg.lineEdit_2.text() salida = entrada + nombre_salida + '/' ## filtrar los ficheros de la carpeta de entrada listaFicheros = os.listdir(entrada) ext = '.shp' listaShp = [] for i in listaFicheros: if os.path.splitext(i)[1] == ext: listaShp.append(i) ## comprobar si existe la capa de salida if os.path.exists(salida): shutil.rmtree(salida) os.mkdir(salida) else: os.mkdir(salida) ## por cada capa de nuestra lista de capas for f in listaShp: ## obtener su nombre sin la extension nombre = os.path.splitext(f)[0] ## cargar el archivo como capa vectorial capa = QgsVectorLayer(entrada + f, nombre, 'ogr') ## definir la ruta de salida completa, incluyendo el nombre y la extension output = salida + nombre + '_exploded' + '.shp' ## generar una barra de progreso cuando se ejecute el plugin iface.messageBar().pushInfo(u'Exploding features', output) ## definir en un diccionario los parametros del algoritmo params = {'INPUT': capa, 'OUTPUT': output} ## ejecutar el algoritmo processing.run('qgis:multiparttosingleparts', params) ## nuevo campo id autoincrementable capa2 = QgsVectorLayer(output,nombre,'ogr') capa2.startEditing() capa2.dataProvider().addAttributes([QgsField('newID', QVariant.Int)]) capa2.updateFields() entidades = capa2.getFeatures() contador = 0 for entidad in entidades: entidad['newID'] = contador capa2.updateFeature(entidad) contador += 1 capa2.commitChanges() ## cargar la capa resultante al proyecto iface.addVectorLayer(output, nombre + '_exploded', 'ogr')
Pondremos a prueba el plugin aplicándolo a dos capas que se encuentran en una carpeta de mi escritorio. Cada una contiene 3 polígonos distintos pero dos de ellos pertenecen a la misma entidad, es decir, son entidades multiparte:
Si abrimos sus atributos comprobamos que, efectivamente, cada una tiene solo 2 entidades y además solo cuentan con un único campo id:
A continuación abrimos el nuevo plugin y seleccionamos la carpeta en la que se encuentran estas dos capas. Además, indicamos que la carpeta en la que se guardarán los resultados (que se ubicará dentro de la carpeta de entrada) se llamará resultados:
Vemos que la carpeta de resultados se creó correctamente y las capas creadas se añadieron automáticamente a QGIS:
Si a continuación abrimos la tabla de atributos, vemos que se creó correctamente el nuevo campo de identificación newID necesario para identificar de forma única cada entidad, puesto que al dividirse las nuevas entidades conservan los atributos de la entidad original:
Gracias a este nuevo campo podemos editar la simbología de las capas nuevas para que cada una esté representada por un color distinto:
Tenéis al plugin al completo en GitHub.