Construirea unui Plugin Python

Atenționare

O nouă versiune a acestui tutorial este disponibilă la Construirea unui Plugin Python (QGIS3)

Plugin-urile reprezintă o modalitate foarte bună de a extinde funcționalitatea QGIS. Puteți scrie plugin-uri care folosesc Python, care pot consta în adăugarea unui simplu buton până la construirea unor instrumente sofisticate. Acest tutorial va evidenția procesul implicat în crearea mediului de dezvoltare, proiectarea interfeței cu utilizatorul pentru un plugin și scrierea de cod pentru interacțiunea cu aplicația QGIS. Vă rugăm să revedeți Noțiuni de Bază Despre Programarea în Python pentru a vă familiariza cu elementele de bază.

Privire de ansamblu asupra activității

Vom dezvolta un plugin simplu numit Salvare Atribute, care va permite utilizatorilor alegerea unui strat vectorial, și scrierea atributelor sale într-un fișier CSV.

Obținerea instrumentelor

Qt Creator

Qt este un cadru de dezvoltare de software, care este utilizat pentru construirea de aplicații care rulează pe Windows, Mac, Linux, precum și pe diferite sisteme de operare mobile. Însuși QGIS este scris folosind cadrul Qt. La dezvoltarea de plugin-uri, vom folosi o aplicație numită Qt Creator pentru a proiecta interfața acestora.

Descărcați și instalați soft-ul Qt Creator de la SourgeForge

Conectarea Python la Qt

Din moment ce dezvoltăm un plugin în Python, trebuie să instalăm legăturile Python pentru Qt. Metoda de instalare a acestora va depinde de platforma pe care o utilizați. Pentru construirea plugin-urilor avem nevoie de instrumentul în linie de comandă pyrcc4.

Windows

Descărcați Programul de instalare din rețea OSGeo4W și alegeți Express Desktop Install. Instalați pachetul QGIS. După instalare, veți putea accesa instrumentul pyrcc4 prin intermediul OSGeo4W Shell.

Mac

Instalați managerul de pachete Homebrew. Instalați pachetul PyQt, prin rularea următoarei comenzi:

brew install pyqt

Linux

În funcție de distribuția dvs., găsiți și instalați pachetul python-qt4. Pe distribuțiile bazate pe Ubuntu și Debian, aveți posibilitatea să executați următoarea comandă:

sudo apt-get install python-qt4

Un editor de text sau un IDE Python

Orice fel de dezvoltare software necesită un editor de text bun. Dacă aveți deja un editor de text favorit sau un IDE (Mediu Integrat de Dezvoltare), îl puteți folosi pentru acest tutorial. În caz contrar, fiecare platformă oferă o largă varietate de opțiuni gratuite, sau plătite, de editoare de text. Alegeți una care se potrivește nevoilor dumneavoastră.

Acest tutorial folosește editorul Notepad++ pe Windows.

Windows

Notepad++ este un bun editor gratuit pentru Windows. Descărcați și instalați editorul Notepad++ <http://notepad-plus-plus.org/repository/6.x/6.7.5/npp.6.7.5.Installer.exe>`_.

Notă

Dacă utilizați Notepad++, asigurați-vă că ați bifat Înlocuiește cu spațiu gol în Setări ‣ Preferințe ‣ Tabulator. Python este foarte sensibil la spațiile albe, iar această setare vă asigură că tab-urile și spațiile sunt tratate în mod corespunzător.

Plugin Builder

Există un plugin QGIS util, denumit Plugin Builder, care creează toate fișierele necesare și șabloanele de cod necesare unui plugin. Găsiți și instalați Plugin Builder. Parcurgeți Utilizarea Plugin-urilor pentru mai multe detalii despre cum se instalează plugin-urile.

Plugins Reloader

Acesta este un alt plugin util, care permite dezvoltarea iterativă a plugin-urilor. Folosind acest plugin, puteți schimba codul plugin-ului și să-l vedeți reflectându-se imediat, fără a reporni QGIS de fiecare dată. Găsiți și instalați Plugin Reloader. Parcurgeți Utilizarea Plugin-urilor pentru mai multe detalii despre instalarea plugin-urilor.

Notă

Plugin Reloader este un plugin experimental. Asigurați-vă că ați bifat Arată, de asemenea, plugin-urile experimentale în setările Managerului de Plugin-uri, dacă nu-l puteți găsi.

Procedura

  1. Deschideți QGIS. Mergeți la Plugins ‣ Plugin Builder ‣ Plugin Builder.

../_images/1164.png
  1. Veți vedea un formular în dialogul QGIS Plugin Builder. Aveți posibilitatea să completați formularul cu datele referitoare la pluginul nostru. În Numele clasei se va introduce numele clasei Python care conține logica plugin-ului. Acesta va fi, de asemenea, și numele de folder care conține toate fișierele plugin-ului. Introduceți SaveAttributes ca nume al clasei. Numele Pluginului este numele sub care plugin-ul va apărea în Managerul de Pluginuri. Introduceți denumirea Save Attributes. Adăugați o descriere în câmpul Descriere. Numele modulului va fi numele principalului fișier python al pluginului. Introduceți-l ca save_attributes. Lăsați numerele de versiune așa cum sunt. Valoarea Textul pentru elementul de meniu va reprezenta modul în care utilizatorii vor găsi plugin-ul în meniul QGIS. Introduceți-l ca Salvare Atribute ca CSV. Introduceți numele dvs. și adresa de e-mail în câmpurile corespunzătoare. Câmpul Meniu va decide locul din QGIS în care este adăugat elementul pluginului. Deoarece pluginul nostru este pentru date vectoriale, selectați Vector. Bifați caseta Marchează pluginul ca experimental din partea de jos. Faceți clic pe OK.

../_images/2136.png
  1. În continuare, vi se va solicita să alegeți un director pentru plugin. Trebuie să navigați la directorul pluginului python QGIS, de pe computer, și să alegeți Selectare Folder. În mod obișnuit, un director .qgis2/ este situat în directorul de casă al utilizatorului. Locația folderului pluginului va depinde de platformă, după cum urmează: (Înlocuiți username cu numele de conectare)

Windows

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

Mac

/Users/username/.qgis2/python/plugins

Linux

/home/username/.qgis2/python/plugins
../_images/378.png
  1. Veți vedea un dialog de confirmare creat o dată cu șablonul de plugin. Rețineți calea către directorul pluginului.

../_images/445.png
  1. Înainte de a putea folosi noul plugin creat, trebuie să compilați fișierul resources.qrc, care a fost creat de către Plugin Builder. Lansați OSGeo4W Shell pe windows, sau aplicația terminal pe Mac sau Linux.

../_images/539.png
  1. Navigați la directorul pluginului, unde a fost creat rezultatul rulării lui Plugin Builder. Puteți utiliza comanda cd urmată de calea către director.

cd c:\Users\username\.qgis2\python\plugins\SaveAttributes
../_images/638.png
  1. O dată ce vă aflați în director, introduceți make. Astfel, se va rula comanda pyrcc4, pe care am instalat-o ca parte a legăturilor Qt pentru Python.

make
../_images/738.png
  1. Acum suntem gata de a arunca o primă privire la noul plugin creat. Închideți QGIS și lansați-l din nou. Mergeți la Pluginuri ‣ Gestiune și Instalare plugin-uri, apoi activați plugin-ul Save Attributes din fila Instalate. Observați că există o nouă pictogramă în bara de instrumente, și o nouă intrare de meniu în Vector ‣ Salvare Atribute ‣ Salvare Atribute ca CSV`. Selectați-l pentru a lansa dialogul plugin-ului.

../_images/837.png
  1. Veți observa un nou dialog, numit Salvare Atribute. Apoi, închideți-l.

../_images/938.png
  1. Vom proiecta acum caseta noastră de dialog și vom adăuga unele elemente de interfață cu utilizatorul la ea. Deschideți programul Qt Creator și mergeți la Fișier –> Deschidere Fișier sau Proiect….

../_images/1045.png
  1. Navigați la folderul pluginului apoi selectați fișierul save_attributes_dialog_base.ui. Faceți clic pe Open.

../_images/1165.png
  1. Veți vedea un dialog gol, în cadrul pluginului. Puteți glisape el elemente din panoul din stânga. Vom adăuga o Casetă combo de tipul Input Widget. Glisați-o pe dialogul pluginului.

../_images/1248.png
  1. Redimensionați caseta combo și ajustați-i dimensiunea. Acum glisați o Etichetă de tip Display Widget în fereastra de dialog.

../_images/1346.png
  1. Faceți clic pe textul etichetei și introduceți Select a layer.

../_images/1443.png
  1. Salvați acest fișier accesând Fișier ‣ Salvați save_attributes_dialog_base.ui. Rețineți că numele obiectului casetă combo este comboBox. Pentru a interacționa cu acest obiect folosind codul python, va trebui să vă referiți la el cu acest nume.

../_images/1540.png
  1. Haideți să reîncărcăm plugin-ul nostru, astfel încât să putem vedea modificările în fereastra de dialog. Mergeți la Plugin ‣ Plugin Reloader ‣ Alegeți un plugin pentru a fi reîncărcat.

../_images/1638.png
  1. Selectați SaveAttributes din dialogul Configurare Încărcător de Plugin-uri.

../_images/1736.png
  1. Acum faceți clic pe butonul Salvează Atributele ca CSV. Veți vedea caseta de dialog nou proiectată.

../_images/1833.png
  1. Haideți să adăugăm plugin-ului ceva logică, pentru a popula caseta combo cu straturile încărcate în QGIS. Mergeți la directorul plugin-ului și încărcați fișierul save_attributes.py într-un editor de text. Derulați în jos pentru a găsi metoda run(self). Această metodă va fi apelată atunci când faceți clic pe butonul din bara de instrumente, sau când selectați elementul de meniu al plugin-ului. Adăugați următorul cod la începutul metodei. Acest cod preia straturile încărcate în QGIS și le adaugă obiectului comboBox din dialogul plugin-ului.

layers = self.iface.legendInterface().layers()
layer_list = []
for layer in layers:
     layer_list.append(layer.name())
     self.dlg.comboBox.addItems(layer_list)
../_images/1925.png
  1. Înapoi în fereastra principală a QGIS, reîncărcați pluginul accesând Pluginuri ‣ Plugin Reloader ‣ Reîncărcare plugin: SaveAttributes. Alternativ, se poate apăsa F5. Pentru a testa această nouă funcționalitate, trebuie să încărcăm câteva straturi în QGIS. După ce încărcați unele date, lansați pluginul accesând Vector ‣ Save Attributes ‣ Save Attributes as CSV.

../_images/2022.png
  1. Veți vedea că acum caseta combo este populată cu numele straturilor care sunt încărcate în QGIS.

../_images/2137.png
  1. Să adăugăm elementele de interfață cu utilizatorul rămase. Reveniți la Qt Creator și încărcați fișierul save_attributes_dialog_base.ui. Adăugați o Etichetă Display Widget și modificați textul în Selectați fișierul de ieșire. Adăugați o LineEdit de tip Input Widget care va arăta calea către fișierul de ieșire pe care l-a ales utilizatorul. Apoi, adăugați un Buton de Apăsare de tip Button și schimbați eticheta butonului în .... Notați numele obiectelor widget, deoarece va trebui să le utilizați pentru a interacționa cu ele. Salvați fișierul.

../_images/2224.png
  1. Vom adăuga codul Python pentru a deschide un explorator de fișiere, atunci când utilizatorul face clic pe butonul ..., și vom afișa calea selectată în widget-ul liniei de editare. Deschideți fișierul save_attributes.py într-un editor de text. Adăugați QFileDialog în lista noastră de importuri, în partea de sus a fișierului.

../_images/2321.png
  1. Adăugați o nouă metodă denumită select_output_file, folosind următorul cod. Acest cod va deschide un explorator de fișiere, și va completa, în linia widget-ului de editare, calea fișierului ales de utilizator.

def select_output_file(self):
    filename = QFileDialog.getSaveFileName(self.dlg, "Select output file ","", '*.txt')
    self.dlg.lineEdit.setText(filename)
../_images/2421.png
  1. Acum, trebuie să adăugăm codul, astfel încât, atunci când butonul ... este apăsat, să fie apelată metoda select_output_file. Derulați până la metoda __init__ și adăugați următoarele linii în partea de jos. Acest cod va șterge textul încărcat anterior (dacă este cazul) din widget-ul liniei de editare, și va conecta, de asemenea, metoda select_output_file la semnalul clicked al widget-ului de tip buton.

self.dlg.lineEdit.clear()
self.dlg.pushButton.clicked.connect(self.select_output_file)
../_images/2520.png
  1. Înapoi în QGIS, reîncărcați pluginul și deschideți dialogul Salvare Atribute`. Dacă totul a mers bine, veți putea face clic pe butonul ... pentru a selecta un fișier text de ieșire de pe disc.

../_images/2618.png
  1. Când faceți clic pe OK din dialogul plugin-ului, nu se întâmplă nimic. Asta pentru că nu am adăugat logica pentru a extrage informații atributelor stratului, și pentru a le scrie în fișierul text. Avem acum toate piesele la locul lor, doar pentru a face asta. Găsiți locul din metoda run unde scrie pass. Înlocuiți-l cu codul de mai jos. Explicațiile acestui cod pot fi găsite în Noțiuni de Bază Despre Programarea î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/2717.png
  1. Acum plugin-ul nostru este gata. Reîncărcați pluginul și încercați-l. Veți afla că fișierul text de ieșire pe care l-ați ales va obține atributele din stratul vectorial. Puteți arhiva directorul plugin-ului,și apoi să-l partajați cu alți utilizatori. Ei pot dezarhiva conținutul în directorul lor de plugin-uri, pentru a-l testa. Dacă ați creat un plugin adevărat, încărcați-l în Depozitul Plugin-urilor QGIS, astfel încât toți utilizatorii QGIS să-l poată găsi și descărca.

Notă

Acest plugin are doar un scop demonstrativ. Nu publicați acest plugin, și nu-l încărcați în depozitul de pluginuri QGIS.

Mai jos este fișierul save_attributes.py complet, pentru referință.

# -*- 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)