Construir una extension Python

Advertencia

Una nueva versión de este tutorial está disponible en 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 sofisticado 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 para familiarizarse con lo básico.

Vision general de la actividad

Desarrollaremos una extension simple llamada «Guardar Atributos» que permitirá al usuario escoger una capa vectorial y escribir sus atributos en un archivo CSV.

Consigue las herramientas

Qt Creator

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 software Qt Creator de SourgeForge

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 pyrcc4.

Windows

Descargue el instalador de red OSGeo4W y elija Express Desktop Install. Instale el paquete QGIS. Después de la instalación, será capaz de acceder a la herramienta pyrcc4 mediante la OSGeo4W Shell.

Mac

Instale el administrador de paquete Homebrew. Instale el paquete PyQt corriendo el siguiente comando:

brew install pyqt

Linux

Dependiendo de tu distribución, busca e instala el paquete «python-qt4». En las distribuciones basadas en Ubuntu y Debian, puedes correr el siguiente comando:

sudo apt-get install python-qt4

Un editor de texto o un entorno de desarrollo integrado de 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 marcar Reemplazar por espacio en Configuración ‣ Preferencias ‣ Configuración de Tabulación. Python es muy sensible a los espacios en blanco y esta configuración asegurará que las tabulaciones y espacios sean tratados apropiadamente.

Complemento 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

Este 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

  1. Abra QGIS. Vaya a Complementos ‣ Plugin Builder ‣ Plugin Builder.

../_images/1180.png
  1. Verá el diálogo QGIS Plugin Builder con un formulario. Puede llenar el formulario con detalles relacionados a nuestro complemento. El Class name será el nombre de la Clase Python que contiene la lógica del complemento. Esto también será el nombre de la carpeta que contiene todos los archivos del complemento. Ingrese SaveAttributes como el nombre de la clase. El Plugin name es el nombre bajo el cual aparecerá el complemento en el Plugin Manager. Ingrese el nombre como Guardar Atributos. Agregue una descripción en el campo Description. El Module name será el nombre del archivo python principal para el complemento. Ingréselo como save_attributes. Deje los números de versión como están. El valor Text for menu item será como los usuarios encontrarán su complemento en el menú QGIS. Ingréselo como Guardar Atributos como CSV. Ingrese su nombre y dirección de correo electrónico en los campos apropiados. El campo Menu decidirá donde es agregado el elemento complemento en QGIS. Debido a que el nuestro es para datos vectoriales, seleccione Vectorial. Marque la casilla Señale el complemento como experimental abajo. Clic en Aceptar.

../_images/2146.png
  1. A continuación, se le pedirá que elija un directorio para su complemento. Necesita navegar al directorio python del complemento QGIS en su computadora y seleccione Seleccione Carpeta. Típicamente, un directorio .qgis2/ está ubicado en su directorio de inicio. La ubicación de la carpeta plugin dependerá de su plataforma como sigue: (Reemplace nombre de usuario con su nombre de acceso)

Windows

c:\Users\username\.qgis2\python\plugins

Mac

/Users/username/.qgis2/python/plugins

Linux

/home/username/.qgis2/python/plugins
../_images/385.png
  1. Verá un diálogo de confirmación una vez que sea creado su plantilla de complemento. Anote la ruta a la carpeta del complemento.

../_images/454.png
  1. Antes que podamos usar el complemento recientemente creado, necesitamos compilar el archivo resources.qrc creado por Plugin Builder. Inicie la OSGeo4W Shell en Windows o una terminal en Mac o Linux.

../_images/547.png
  1. Explore el directorio de complemento donde fue creada la salida de Plugin Builder. Puede usar el comando cd seguido por la ruta del directorio.

cd c:\Users\username\.qgis2\python\plugins\SaveAttributes
../_images/646.png
  1. Una vez que esté en el directorio, escriba make. Esto ejecutará el comando pyrcc4 que habíamos instalado como parte de Qt bindings para Python.

make
../_images/746.png
  1. Ahora estamos listos para dar una primera mirada al complemento nuevo que creamos. Cierre QGIS e inícielo de nuevo. Vaya a Complemento ‣ Administrar e Instalar complementos y active el complemento Guardar Atributos en la pestaña Instalado. Notará que hay un nuevo icono en la barra de herramientas y una nueva entrada de menú bajo Vectorial ‣ Guardar Atributos ‣ Guardar Atributos como CSV`.

../_images/845.png
  1. Notará un nuevo diálogo en blanco llamado Guardar Atributos. Cierre este diálogo.

../_images/946.png
  1. Ahora diseñaremos nuestra caja de diálogo y le agregaremos algunos elementos de interfaz de usuario. Abra el programa Qt Creator y vaya a Archivo –> Abrir Archivo o Proyecto….

../_images/1053.png
  1. Explore el directorio del complemento y seleccione el archivo save_attributes_dialog_base.ui. Clic en Abrir.

../_images/1181.png
  1. Verá al diálogo vacío 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.

../_images/1256.png
  1. Redimensione la caja combinada y ajuste su tamaño. Ahora arrastre una Etiqueta tipo Display Widget en el diálogo.

../_images/1354.png
  1. Selecciona la etiqueta del texto y escribe «Seleccionar una capa».

../_images/1450.png
  1. Guarde este archivo yendo a Archivo ‣ Guardar save_attributes_dialog_base.ui. Note que el nombre del objeto caja combo es comboBox. Para interactuar con este objeto uso código python, tendremos que referirnos a él por su nombre.

../_images/1544.png
  1. Volvamos a cargar de nuevo nuestro complemento de manera que podamos ver los cambios en la ventana de diálogo. Vaya a Complemento ‣ Plugin Reloader ‣ Escoger un complemento a ser recargado.

../_images/1642.png
  1. Seleccione GuardarAtributos en el diálogo Configurar Plugin reloader.

../_images/1740.png
  1. Ahora clic en el botón Guardar Atributos como CSV. Verá la caja de diálogo recientemente diseñada.

../_images/1836.png
  1. Agreguemos alguna 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 un editor de texto. Deslícese hacia abajo y encuentre el método run(self). Este método será llamado cuando haga clic en el botón de la barra de herramientas o seleccione el elemento complemento del menú. Agregue el siguiente código al inicio del método. Este código hace que se cargue las capas en QGIS y las agrega al objeto comboBox del diálogo del complemento.

layers = self.iface.legendInterface().layers()
layer_list = []
for layer in layers:
     layer_list.append(layer.name())
     self.dlg.comboBox.addItems(layer_list)
../_images/1927.png
  1. De vuelta en la ventana principal de QGIS, recargue el complemento yendo a Complementos ‣ Plugin Reloader ‣ Reload plugin: SaveAttributes. Alternativamente, puede presionar F5. Para probar esta nueva funcionalidad, debemos cargar algunas capas en QGIS. Después de que cargue algunos datos, inicie el complemento yendo a Vectorial ‣ Guardar Atributos ‣ Guardar Atributos como CSV.

../_images/2024.png
  1. Verá que nuestra caja combo ahora está poblada con los nombres de capa que son cargados en QGIS.

../_images/2147.png
  1. 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 tipo Label Widget de Despliegue y cambie el texto a Seleccione el archivo de salida. Agregue un tipo LineEditWidget de Entrada que mostrará la ruta del archivo de salida que el usuario ha elegido. A continuación, agregue un Botón tipo Push Button 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.

../_images/2225.png
  1. 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 archivo save_attributes.py en el editor de texto. Agregue QFileDialog a nuestra lista de importaciones en el tope del archivo.

../_images/2322.png
  1. 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 de edición de línea con la ruta del archivo que el usuario escoja.

def select_output_file(self):
    filename = QFileDialog.getSaveFileName(self.dlg, "Select output file ","", '*.txt')
    self.dlg.lineEdit.setText(filename)
../_images/2422.png
  1. Ahora necesitamos agregar código de manera que cuando se hace clic en el botón , el método select_output_file sea llamado. Deslice hacia arriba hasta el método __init__ y agregue las siguientes líneas debajo. Este código limpiará el texto previamente cargado (si hubiera) en la línea widget de edición y también conectará el método selec_output_file a la señal clicked del widget botón de presión.

self.dlg.lineEdit.clear()
self.dlg.pushButton.clicked.connect(self.select_output_file)
../_images/2521.png
  1. De vuelta en QGIS, recargue el complemento y abra el diálogo Guardar Atributos. Si todo va bien, será capaz de hacer clic en el botón .. y seleccionar un archivo texto de salida de su disco.

../_images/2618.png
  1. Cuando hace clic en Aceptar en el diálogo del complemento, no pasa nada. Esto es debido a que no hemos agregado la lógica para extraer la información de atributo de la capa y escribirlo al archivo texto. Ahora tenemos todas las piezas en su lugar para hacer justo eso. Encuentre el lugar en el método run donde dice pass. 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.

filename = self.dlg.lineEdit.text()
output_file = open(filename, 'w')

selectedLayerIndex = self.dlg.comboBox.currentIndex()
selectedLayer = layers[selectedLayerIndex]
fields = selectedLayer.pendingFields()
fieldnames = [field.name() for field in fields]

for f in selectedLayer.getFeatures():
    line = ','.join(unicode(f[x]) for x in fieldnames) + '\n'
    unicode_line = line.encode('utf-8')
    output_file.write(unicode_line)
output_file.close()
../_images/2718.png
  1. Ahora nuestro complemento está listo. Recargue el complemento y pruébelo. Encontrará que el archivo texto de salida que elige tendrá los atributos de la capa vectorial. Puede comprimir el directorio del complemento y compartirlo con sus usuarios. Ellos pueden descomprimir el contenido en su directorio de complemento y probar su complemento. Si fuera un complemento real, usted lo subiría al Repositio de Complemento QGIS para que todos los usuarios QGIS sean capaces de encontrar y descargar su complemento.

Nota

Esta extension es solamente para propósitos de demostración. No publicar esta extension o subirla al repositorio de extensiones de QGIS.

Abajo esta el archivo entero de «save_attributes.py» como referencia.

# -*- coding: utf-8 -*-
"""
/***************************************************************************
 SaveAttributes
                                 A QGIS plugin
 This plugin saves the attribute of the selected vector layer as a CSV file.
                              -------------------
        begin                : 2015-04-20
        git sha              : $Format:%H$
        copyright            : (C) 2015 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 PyQt4.QtCore import QSettings, QTranslator, qVersion, QCoreApplication
from PyQt4.QtGui import QAction, QIcon, QFileDialog
# Initialize Qt resources from file resources.py
import resources_rc
# 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)

        # Create the dialog (after translation) and keep reference
        self.dlg = SaveAttributesDialog()

        # Declare instance attributes
        self.actions = []
        self.menu = self.tr(u'&Save Attributes')
        # TODO: We are going to let the user set this up in a future iteration
        self.toolbar = self.iface.addToolBar(u'SaveAttributes')
        self.toolbar.setObjectName(u'SaveAttributes')
        
        self.dlg.lineEdit.clear()
        self.dlg.pushButton.clicked.connect(self.select_output_file)
        

    # 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:
            self.toolbar.addAction(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/SaveAttributes/icon.png'
        self.add_action(
            icon_path,
            text=self.tr(u'Save Attributes as CSV'),
            callback=self.run,
            parent=self.iface.mainWindow())


    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)
        # remove the toolbar
        del self.toolbar

    def select_output_file(self):
        filename = QFileDialog.getSaveFileName(self.dlg, "Select output file ","", '*.txt')
        self.dlg.lineEdit.setText(filename)
        
    def run(self):
        """Run method that performs all the real work"""
        layers = self.iface.legendInterface().layers()
        layer_list = []
        for layer in layers:
                layer_list.append(layer.name())
            
        self.dlg.comboBox.clear()
        self.dlg.comboBox.addItems(layer_list)
        # show the dialog
        self.dlg.show()
        # Run the dialog event loop
        result = self.dlg.exec_()
        # See if OK was pressed
        if result:
            # Do something useful here - delete the line containing pass and
            # substitute with your code.
            filename = self.dlg.lineEdit.text()
            output_file = open(filename, 'w')
           
            selectedLayerIndex = self.dlg.comboBox.currentIndex()
            selectedLayer = layers[selectedLayerIndex]
            fields = selectedLayer.pendingFields()
            fieldnames = [field.name() for field in fields]
            
            for f in selectedLayer.getFeatures():
                line = ','.join(unicode(f[x]) for x in fieldnames) + '\n'
                unicode_line = line.encode('utf-8')
                output_file.write(unicode_line)
            output_file.close()

If you want to give feedback or share your experience with this tutorial, please comment below. (requires GitHub account)