Construyendo un Complemento Python (QGIS3)¶
Los complementos son una gran manera de extender la funcionalidad de QGIS. Puede escribir complementos usando Python que van desde la adición de un simple botón a conjuntos sofisticados de herramientas. Este tutorial destacará el proceso involucrado en establecer tu ambiente de desarrollo, diseñar el interfaz de usuario para un complemento y escribir código para interactuar con QGIS. Por favor revise el tutorial Iniciándote con la Programación Python (QGIS3) para familiarizarse con lo básico.
Nota
Si está construyendo un nuevo complemento, recomiendo fuertemente construir un Complemento Procesos en vez del complemento GUI descrito en este tutorial. Vea Construyendo un Complemento de Procesamiento (QGIS3) para más detalles.
Vista general de la tarea¶
Desarrollaremos un complemento simple llamado Guardar Atributos
que permitirá a los usuarios escoger una capa vectorial y escribir sus atributos como un archivo CSV.
Obtener las Herramientas¶
Creador Qt¶
Qt es un marco de desarrollo de software que es usado para desarrollar aplicaciones que corren en Windows, Mac, Linux así como varios sistemas operativos móviles. QGIS mismo está escrito usando el marco Qt. Para el desarrollo de complemento, usaremos una aplicación llamada Qt Creator para diseñar el interfaz para nuestro complemento.
Descargue e instale el instalador de Qt Creator de Qt Offline Installers. Asegúrese que selecciona Qt Creator en la página de descarga. Note que tendrá que crear una cuenta libre de Qt para instalar el paquete.
Nota
El instalador OSGeo4w para QGIS en Windows incluye una copia del programa Qt Designer que es una versión liviana de Qt Creator y es perfectamente apropiada para construir complementos. Puede saltarse la descarga de Qt Creator y usar en vez de ello
.Vínculos Python para Qt¶
Ya que estamos desarrollando el complemento en Python, necesitamos instalar los vínculos python para Qt. El método para instalar estos dependerá de la plataforma que está usando. Para construir complementos necesitamos la herramienta de línea de comando pyrcc5
.
Windows
Los enlaces python relevantes están incluidos en la instalación de QGIS en Windows. Pero para usarlos desde la carpeta de complementos, necesitamos indicar la ruta a la instalación de QGIS.
Cree un archivo de lotes Windows (extensión .bat) con el siguiente contenido y guárdelo en su computadora como compile.bat
. Copiaremos después este archivo a la carpeta de complemento. Si instaló QGIS en una ruta diferente, reemplace C:\OSGeo4W64\bin\
con su ruta.
@echo off
call "C:\OSGeo4W64\bin\o4w_env.bat"
call "C:\OSGeo4W64\bin\qt5_env.bat"
call "C:\OSGeo4W64\bin\py3_env.bat"
@echo on
pyrcc5 -o resources.py resources.qrc
Mac
Instale el administrador de paquete Homebrew. Instale el paquete PyQt
corriendo el siguiente comando:
brew install pyqt
- Linux
Dependiendo de su Distribución, encuentre e instale el paquete
python-qt5
. En Ubuntu y distribuciones basadas en Debian, puede correr el siguiente comando:
sudo apt-get install python-qt5
Nota
Puede encontrar que QGIS ya ha instalado este paquete.
Un Editor de Texto o un IDE Python.¶
Cualquier tipo de desarrollo de software requiere un buen editor de texto. Si ya tiene un editor de texto favorito o un IDE (Ambiente Integrado de Desarrollo, en inglés), puede usarlo para este tutorial. De otra forma, cada plataforma ofrece una amplia variedad de opciones gratuitas o pagadas para editores de texto. Elija aquel que se ajuste a sus necesidades.
Este tutorial usa el editor Notepad++ en Windows.
Windows
Notepad++ es un buen editor gratuito para windows. Descargue e instale el editor Notepad++.
Nota
Si está usando Notepad++, asegúrese de ir a Reemplazar por espacio. Python es muy sensible al espacio en blanco y esta configuración asegurará que las tabulaciones y espacios sean tratadas apropiadamente.
y activeComplemento Plugin Builder¶
Hay un complemento útil QGIS llamado Plugin Builder
que crea todos los archivos necesarios y el código repetitivo para un complemento. Encuentre e instale el complemento Plugin Builder
. Vea Uso de Complementos para más detalles de como instalar complementos.
Complemento Plugins Reloader¶
Éste es otro complemento asistente que permite el desarrollo iterativo de complementos. Usando este complemento, puede cambiar el código del complemento y tenerlo reflejado en QGIS sin tener que reiniciar QGIS cada vez. Encuentre e instale el complemento Plugin Reloader
. Vea Uso de Complementos para más detalles sobre como instalar complementos.
Nota
Plugin Reloader es un complemento experimental. Asegúrese que ha marcado Mostrar también complementos experimentales en la configuración Administrador de Complementos si no puede encontrarlo.
Procedimiento¶
Abra QGIS. Vaya a
.
Verá el diálogo QGIS Plugin Builder con un formulario. Puede llenar el formulario con detalles relacionados a nuestro complemento. El Nombre de clase será el nombre de la Clase Python que contiene la lógica del complemento. Éste también será el nombre de la carpeta que contiene todos los archivos de complementos. Ingrese
SaveAttributes
como el nombre de la clase. El Nombre de complemento es el nombre bajo el que aparecerá el complemento en el Administrador de Complemento. Ingrese el nombre comoGuardar Attributos
. Agregue una descripción en el campo Descripción. El Nombre del módulo será el nombre del archivo python principal para el complemento. Ingréselo comosave_attributes_processing
. Deje los números de versión como están e ingrese su nombre y dirección electrónica en los campos apropiados. Clic en Siguiente.
Ingrese una breve descripción del complemento para el diálogo Acerca y clic en Siguiente.
Seleccione el
Botón de herramienta con diálogo
del selector Plantilla. El valor Texto para el elemento de menú será como encontrarán los usuarios el complemento en el menú QGIS. Ingréselo comoGuardar Atributos como CSV
. El campo Menu decidirá dónde es agregado el elemento complemento en QGIS. Debido a que el complemento es para datos vectoriales, seleccioneVectorial
. Clic en Siguiente.
El Plugin Builder le pedirá el tipo de archivos a generar. Mantenga la selección predeterminada y clic en Siguiente.
Como no pretendemos publicar el complemento, puede dejar los valores Rastreador de errores, Repositorio y Página de inicio predeterminados. Marque la caja Señale el complemento como experimental abajo y clic en Siguiente.
Se le pedirá escoger un directorio para su complemento. Por ahora, guárdelo en un directorio que pueda encontrar fácilmente en su computadora y clic en Generar.
A continuación, presione el botón generar. Verá un diálogo de confirmación una vez que la plantilla de complemento sea creada.
Nota
Puede que vea un aviso que dice que no se encontró pyrcc5 en su ruta. Puede ignorar este mensaje.
Antes que podamos usar este complemento recién creado, necesitamos compilar el archivo
resources.qrc
creado con Plugin Builder. Este archivo es parte del Qt Resource System que referencia a todos los archivos binarios usados en el complemento. Para este complemento, sólo tendrá el icono del complemento. Compilando este archivo genera el código de la aplicación que puede ser usado en el complemento independiente de en que plataforma se corra el complemento. Siga las instrucciones específicas de la plataforma para este paso.
Windows
Ahora puede copiar el archivo compile.bat
(creado durante la sección Python Bindings for Qt al inicio) a la carpeta del complemento. Una vez copiado, doble-clic en el archivo para ejecutarlo. Si la ejecución es exitosa, verá un nuevo archivo llamado resources.py
en la carpeta.
Nota
Si este paso falla, puede iniciar cmd.exe
y explorar la carpeta del complemento usando el comando cd
. Ejecute el archivo de procesamiento por lotes ejecutando compile.bat
para ver el error.
Mac y Linux
Necesitará instalar primero pb_tool
. Abra una Terminal e instálelo mediante pip.
sudo pip3 install pb_tool
Abra una Terminal y vaya al directorio del complemento y escriba pb_tool compile
. Esto ejecutará el comando pyrcc5
que instalamos como parte de la sección Python Bindings for Qt.
pb_tool compile
Los complemento en QGIS son almacenados en una carpeta especial. Debemos copiar nuestro directorio de complemento a esa carpeta antes que podamos usarlo. En QGIS, localice la carpeta de su perfil actual yendo a
.
En la carpeta del perfil, copie la carpeta del complemento a la subcarpeta
Ahora estamos listos para darle una primera mirada al complemento nuevo que creamos. Cierre QGIS e inícielo de nuevo. Vaya a
y active el complementoGuardar Atributos
en la pestaña Instalado.
Notará que hay un nuevo icono en la barra de herramientas del complemento y una nueva entrada de menú bajo
.
Notará un nuevo diálogo blanco llamado Guardar Atributos. Cierre este diálogo.
Ahora diseñaremos nuestra caja de diálogo y agregaremos algunos elementos de interfaz de usuario. Abra el programa
Qt Creator
y vaya a Archivo –> Abrir Archivo o Proyecto….
Explore el directorio del complemento y seleccione el archivo
save_attributes_dialog_base.ui
. Clic en Abrir.
Nota
Windows esconde la carpeta AppData
por lo que no puede verla en el diálogo de selector de archivo. Puede ingresar AppData
en el aviso Nombre de archivo de su directorio padre para abrirlo.
Verá al diálogo blanco del complemento. Puede arrastrar y soltar elementos del panel de la izquierda en el diálogo. Agregaremos un tipo Caja Combo de Widgets de Entrada. Arrástrela al diálogo del complemento.
Redimensione la caja combo y ajuste su tamaño. Ahora arrastre una Etiqueta tipo Widget de Despliegue en el diálogo.
Clic en el texto de etiqueta e ingrese
Seleccione una capa
.
Guarde este archivo yendo a
. Note que el nombre del objeto caja combo escomboBox
. Para interactuar con este objeto use código python, tendremos que referirnos a él por su nombre.
Volvamos a cargar nuestro complemento de manera que podamos ver los cambios en la ventana de diálogo. Vaya a
. SeleccioneSaveAttributes
en el diálogo Configurar Plugin reloader.
Clic el botón Recargar complemento para cargar la última versión del complemento. Clic al botón Guardar Atributos como CSV para abrir la caja de diálogo recién diseñada.
Agreguemos algo de lógica al complemento que poblará la caja combo con las capas cargadas en QGIS. Vaya al directorio del complemento y cargue el archivo
save_attributes.py
en el editor de texto. Primero, inserte en la cima del archivo con las otras importaciones:from qgis.core import QgsProject
Luego deslice hacia abajo hasta el final y encuentre el método
run(self)
. Este método será llamado cuando se haga clic al botón barra de herramientas o se seleccione el elemento de menú del complemento. Agregue el siguiente código al inicio del método. Este código hace que las capas se carguen en QGIS y las agrega al objetocomboBox
del diálogo de complemento.# Fetch the currently loaded layers layers = QgsProject.instance().layerTreeRoot().children() # Clear the contents of the comboBox from previous runs self.dlg.comboBox.clear() # Populate the comboBox with names of all the loaded layers self.dlg.comboBox.addItems([layer.name() for layer in layers])
De vuelta en la ventana principal QGIS, recarge el complemento haciendo clic en el botón Recargar complemento. Para probar esta nueva funcionalidad, debemos cargar algunas capas en QGIS. Después de que haya cargado algunas capa, inicie el complemento yendo a . Verá que nuestra caja combo está ahora poblada con los nombres de capas que están cargadas en QGIS.
Agreguemos los elementos restantes del interfaz de usuario. Cámbiese de vuelta a Qt Creator y carge el archivo
save_attributes_dialog_base.ui
. Agregue un tipoLabel
Widget de Despliegue y cambie el texto aSeleccione el archivo de salida
. Agregue un tipoLineEdit
Widget de Entrada que mostrará la ruta del archivo de salida que el usuario ha elegido. A continuación, agregue un tipoPush Button
Botón y cambie la etiqueta del botón a...
. Anote los nombres de objeto de los widgets que tendremos que usar para interactuar con ellos. Guarde el archivo.
Ahora agregaremos código python para abrir un explorador de archivo cuando el usuario hace clic en el botón de presión
...
y mostrar la ruta de selección en el widget línea de edición. Abra el archivosave_attributes.py
en el editor de texto. AgregueQFileDialog
a la lista de importacionesQtWidgets
en el topo del archivo.
Agregue un nuevo método llamado
select_output_file
con el siguiente código. Este código abrirá un explorador de archivo y poblará el widget línea de edición con la ruta del archivo que el usuario escoja. Note comogetSaveFileName
devuelve una tupla con el nombre de archivo y el filtro usado.
def select_output_file(self): filename, _filter = QFileDialog.getSaveFileName( self.dlg, "Select output file ","", '*.csv') self.dlg.lineEdit.setText(filename)
Ahora necesitamos agregar código de manera que cuando el botón … sea presionado, se llame al método
select_output_file
. Deslice hacia abajo al métodorun
y agregue la siguiente línea en el bloque donde se inicializa el diálogo. Este código conectará el métodoselect_output_file
a la señalclicked
del widget botón de presión.
self.dlg.pushButton.clicked.connect(self.select_output_file)
De vuelta en QGIS, recargue el complemento y ejecútelo. Si todo va bien, será capaz de hacer clic en el botón
...
y seleccionar un archivo texto de salida de su disco.
Cuando hace clic en Aceptar en el diálogo de complemento, no pasa nada. Esteo es debido a que no hemos agregado la lógica para extraer la información de atributo de la capa y escribirla al archivo texto. Ahora tenemos todas las piezas en su lugar para hacer eso. Encuentre el lugar en el método
run
donde dicepass
. Reemplácelo con el código de abajo. La explicación para este código puede encontrarse en Iniciándote con la Programación Python (QGIS3).filename = self.dlg.lineEdit.text() with open(filename, 'w') as output_file: selectedLayerIndex = self.dlg.comboBox.currentIndex() selectedLayer = layers[selectedLayerIndex].layer() fieldnames = [field.name() for field in selectedLayer.fields()] # write header line = ','.join(name for name in fieldnames) + '\n' output_file.write(line) # write feature attributes for f in selectedLayer.getFeatures(): line = ','.join(str(f[name]) for name in fieldnames) + '\n' output_file.write(line)
Tenemos una última cosa para agregar. Cuando las operaciones terminan exitosamente, deberíamos indicar los mismo al usuario. La manera preferida para dar notificaciones al usuario en QGIS es mediante el método
self.iface.messageBar().pushMessage()
. AgregueQgis
a la listaqgis.core
de importaciones en la parte de arriba del archivo y agregue el código de abajo al final del métodorun
.
self.iface.messageBar().pushMessage( "Success", "Output file written at " + filename, level=Qgis.Success, duration=3)
Ahora nuestro complemento está listo. Recargue el complemento y pruébelo. Encontrará que el archivo texto de salida tendrá los atributos de la capa vectorial.
Puede comprimir el directorio del complemento y compartirlo con sus usuarios. Ellos pueden descomprimir el contenido a su directorio de complemento y probar su complemento. Si esto fuera un complemento real, usted lo actualizaría al Repositorio de Complementos QGIS para que todos los usuarios QGIS sean capaces de encontrar y descargar su complemento.
Nota
Este complemento es sólo para fines de demonstración. No publique este complemento ni lo suba al repositorio de complementos de QGIS.
Abajo está el archivo entero de «save_attributes.py» como referencia.
# -*- coding: utf-8 -*-
"""
/***************************************************************************
SaveAttributes
A QGIS plugin
This plugin saves the attributes of the selected vector layer as a CSV file.
Generated by Plugin Builder: http://g-sherman.github.io/Qgis-Plugin-Builder/
-------------------
begin : 2019-03-28
git sha : $Format:%H$
copyright : (C) 2019 by Ujaval Gandhi
email : ujaval@spatialthoughts.com
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
"""
from PyQt5.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QFileDialog
from qgis.core import QgsProject, Qgis
# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .save_attributes_dialog import SaveAttributesDialog
import os.path
class SaveAttributes:
"""QGIS Plugin Implementation."""
def __init__(self, iface):
"""Constructor.
:param iface: An interface instance that will be passed to this class
which provides the hook by which you can manipulate the QGIS
application at run time.
:type iface: QgsInterface
"""
# Save reference to the QGIS interface
self.iface = iface
# initialize plugin directory
self.plugin_dir = os.path.dirname(__file__)
# initialize locale
locale = QSettings().value('locale/userLocale')[0:2]
locale_path = os.path.join(
self.plugin_dir,
'i18n',
'SaveAttributes_{}.qm'.format(locale))
if os.path.exists(locale_path):
self.translator = QTranslator()
self.translator.load(locale_path)
if qVersion() > '4.3.3':
QCoreApplication.installTranslator(self.translator)
# Declare instance attributes
self.actions = []
self.menu = self.tr(u'&Save Attributes')
# Check if plugin was started the first time in current QGIS session
# Must be set in initGui() to survive plugin reloads
self.first_start = None
# noinspection PyMethodMayBeStatic
def tr(self, message):
"""Get the translation for a string using Qt translation API.
We implement this ourselves since we do not inherit QObject.
:param message: String for translation.
:type message: str, QString
:returns: Translated version of message.
:rtype: QString
"""
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
return QCoreApplication.translate('SaveAttributes', message)
def add_action(
self,
icon_path,
text,
callback,
enabled_flag=True,
add_to_menu=True,
add_to_toolbar=True,
status_tip=None,
whats_this=None,
parent=None):
"""Add a toolbar icon to the toolbar.
:param icon_path: Path to the icon for this action. Can be a resource
path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
:type icon_path: str
:param text: Text that should be shown in menu items for this action.
:type text: str
:param callback: Function to be called when the action is triggered.
:type callback: function
:param enabled_flag: A flag indicating if the action should be enabled
by default. Defaults to True.
:type enabled_flag: bool
:param add_to_menu: Flag indicating whether the action should also
be added to the menu. Defaults to True.
:type add_to_menu: bool
:param add_to_toolbar: Flag indicating whether the action should also
be added to the toolbar. Defaults to True.
:type add_to_toolbar: bool
:param status_tip: Optional text to show in a popup when mouse pointer
hovers over the action.
:type status_tip: str
:param parent: Parent widget for the new action. Defaults None.
:type parent: QWidget
:param whats_this: Optional text to show in the status bar when the
mouse pointer hovers over the action.
:returns: The action that was created. Note that the action is also
added to self.actions list.
:rtype: QAction
"""
icon = QIcon(icon_path)
action = QAction(icon, text, parent)
action.triggered.connect(callback)
action.setEnabled(enabled_flag)
if status_tip is not None:
action.setStatusTip(status_tip)
if whats_this is not None:
action.setWhatsThis(whats_this)
if add_to_toolbar:
# Adds plugin icon to Plugins toolbar
self.iface.addToolBarIcon(action)
if add_to_menu:
self.iface.addPluginToVectorMenu(
self.menu,
action)
self.actions.append(action)
return action
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
icon_path = ':/plugins/save_attributes/icon.png'
self.add_action(
icon_path,
text=self.tr(u'Save Attributes as CSV'),
callback=self.run,
parent=self.iface.mainWindow())
# will be set False in run()
self.first_start = True
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
for action in self.actions:
self.iface.removePluginVectorMenu(
self.tr(u'&Save Attributes'),
action)
self.iface.removeToolBarIcon(action)
def select_output_file(self):
filename, _filter = QFileDialog.getSaveFileName(
self.dlg, "Select output file ","", '*.csv')
self.dlg.lineEdit.setText(filename)
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 = SaveAttributesDialog()
self.dlg.pushButton.clicked.connect(self.select_output_file)
# Fetch the currently loaded layers
layers = QgsProject.instance().layerTreeRoot().children()
# Clear the contents of the comboBox and lineEdit from previous runs
self.dlg.comboBox.clear()
self.dlg.lineEdit.clear()
# Populate the comboBox with names of all the loaded layers
self.dlg.comboBox.addItems([layer.name() for layer in layers])
# show the dialog
self.dlg.show()
# Run the dialog event loop
result = self.dlg.exec_()
# See if OK was pressed
if result:
filename = self.dlg.lineEdit.text()
with open(filename, 'w') as output_file:
selectedLayerIndex = self.dlg.comboBox.currentIndex()
selectedLayer = layers[selectedLayerIndex].layer()
fieldnames = [field.name() for field in selectedLayer.fields()]
# write header
line = ','.join(name for name in fieldnames) + '\n'
output_file.write(line)
# write feature attributes
for f in selectedLayer.getFeatures():
line = ','.join(str(f[name]) for name in fieldnames) + '\n'
output_file.write(line)
self.iface.messageBar().pushMessage(
"Success", "Output file written at " + filename,
level=Qgis.Success, duration=3)
If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)