ساخت پلاگین پایتون

هشدار

نسخه جدید این آموزش در ساخت پلاگین پایتون (QGIS3) موجود است.

پلاگین ها یک راه عالی برای گسترش عملکرد QGIS هستند. می‌توانید با استفاده از پایتون پلاگین‌هایی بنویسید که می‌توانند از افزودن یک دکمه ساده تا جعبه‌ابزارهای پیچیده متغیر باشند. این آموزش به تشریح مراحل مربوط به راه اندازی محیط توسعه، طراحی رابط کاربری برای یک افزونه و نوشتن کد برای تعامل با QGIS می پردازد. لطفاً آموزش :doc:`getting_started_with_pyqgis را مرور کنید تا با اصول اولیه آشنا شوید.

بررسی اجمالی کار

ما یک پلاگین ساده به نام "ذخیره ویژگی ها" ایجاد خواهیم کرد که به کاربران امکان می دهد یک لایه برداری را انتخاب کنند و ویژگی های آن را در یک فایل CSV بنویسند.

ابزارها را دریافت کنید

خالق Qt

Qt یک چارچوب توسعه نرم افزار است که برای توسعه برنامه هایی که بر روی ویندوز، مک، لینوکس و همچنین سیستم عامل های مختلف تلفن همراه اجرا می شوند، استفاده می شود. خود QGIS با استفاده از چارچوب Qt نوشته شده است. برای توسعه افزونه، ما از یک برنامه کاربردی به نام «Qt Creator <http://doc.qt.io/qtcreator/index.html>`_ برای طراحی رابط برای افزونه خود استفاده خواهیم کرد.

نرم افزار Qt Creator را از SourgeForge دانلود و نصب کنید

پیوندهای پایتون برای Qt

از آنجایی که ما در حال توسعه افزونه در پایتون هستیم، باید پیوندهای پایتون را برای Qt نصب کنیم. روش نصب اینها به پلتفرمی که استفاده می کنید بستگی دارد. برای ساخت پلاگین ها به ابزار خط فرمان «pyrcc4» نیاز داریم.

پنجره ها

"نصب کننده شبکه OSGeo4W <http://trac.osgeo.org/osgeo4w/>`_ را دانلود کنید و :guilabel:"Express Desktop Install" را انتخاب کنید. بسته "QGIS" را نصب کنید. پس از نصب، می توانید از طریق OSGeo4W Shell به ابزار ``pyrcc4` دسترسی پیدا کنید.

مک

مدیر بسته Homebrew <http://brew.sh>`_ را نصب کنید. با اجرای دستور زیر بسته «PyQt» را نصب کنید:

brew install pyqt

لینوکس

بسته به توزیع خود، بسته «python-qt4» را پیدا و نصب کنید. در توزیع های مبتنی بر اوبونتو و دبیان، می توانید دستور زیر را اجرا کنید:

sudo apt-get install python-qt4

یک ویرایشگر متن یا یک IDE پایتون

هر نوع توسعه نرم افزاری نیاز به یک ویرایشگر متن خوب دارد. اگر قبلاً یک ویرایشگر متن مورد علاقه یا یک IDE (محیط توسعه یکپارچه) دارید، می توانید از آن برای این آموزش استفاده کنید. در غیر این صورت، هر پلتفرم طیف گسترده ای از گزینه های رایگان یا پولی را برای ویرایشگرهای متن ارائه می دهد. یکی را انتخاب کنید که متناسب با نیاز شما باشد.

این آموزش از ویرایشگر Notepad++ در ویندوز استفاده می کند.

پنجره ها

'Notepad++ <http://notepad-plus-plus.org/>`_ یک ویرایشگر رایگان خوب برای ویندوز است. ویرایشگر Notepad++ <http://notepad-plus-plus.org/repository/6.x/6.7.5/npp.6.7.5.Installer.exe>`_ را دانلود و نصب کنید.

توجه

اگر از Notepad++ استفاده می‌کنید، حتماً :guilabel:'Replace by space' را در :menuselection:'Settings --> Preferences --> Tab Settings' بررسی کنید. پایتون در مورد فضای سفید بسیار حساس است و این تنظیم باعث می شود که برگه ها و فضاها به درستی رفتار شوند.

پلاگین افزونه ساز

یک پلاگین QGIS مفید به نام "Plugin Builder" وجود دارد که تمام فایل های لازم و کد دیگ بخار را برای یک پلاگین ایجاد می کند. افزونه «Plugin Builder» را پیدا و نصب کنید. برای جزئیات بیشتر در مورد نحوه نصب افزونه ها به استفاده از پلاگین Plugins مراجعه کنید.

افزونه Plugins Reloader

این افزونه کمکی دیگری است که امکان توسعه تکراری پلاگین ها را فراهم می کند. با استفاده از این افزونه، می توانید کد پلاگین خود را تغییر دهید و بدون نیاز به راه اندازی مجدد QGIS هر بار، آن را در QGIS منعکس کنید. افزونه «Plugin Reloader» را پیدا و نصب کنید. برای جزئیات بیشتر در مورد نحوه نصب افزونه ها به استفاده از پلاگین Plugins مراجعه کنید.

توجه

Plugin Reloader یک افزونه آزمایشی است. اگر نمی توانید آن را پیدا کنید، مطمئن شوید که :guilabel: نمایش افزونه های آزمایشی در تنظیمات :guilabel:`Plugin Manager را علامت زده اید.

روش

  1. QGIS را باز کنید. به Plugins ‣ Plugin Builder ‣ Plugin Builder بروید.

../_images/1168.png
  1. گفتگوی :guilabel:'QGIS Plugin Builder' را با یک فرم مشاهده خواهید کرد. می توانید فرم را با جزئیات مربوط به افزونه ما پر کنید. :guilabel:'Class name' نام کلاس پایتون خواهد بود که حاوی منطق پلاگین است. این نام پوشه حاوی تمام فایل های افزونه نیز خواهد بود. "SaveAttributes" را به عنوان نام کلاس وارد کنید. Plugin name' نامی است که پلاگین شما تحت آن در :guilabel:`Plugin Manager ظاهر می شود. نام را به عنوان "ذخیره ویژگی ها" وارد کنید. در قسمت :guilabel:'Description' توضیحاتی اضافه کنید. :guilabel:`نام ماژول، نام فایل اصلی پایتون برای افزونه خواهد بود. آن را به عنوان «save_attributes» وارد کنید. شماره نسخه ها را همانطور که هستند بگذارید. مقدار :guilabel:'Text for menu item' نحوه یافتن افزونه شما توسط کاربران در منوی QGIS خواهد بود. آن را به‌عنوان «ذخیره ویژگی‌ها به‌عنوان CSV» وارد کنید. نام و آدرس ایمیل خود را در فیلدهای مربوطه وارد کنید. فیلد :guilabel:`Menu تصمیم می‌گیرد که آیتم افزونه شما در QGIS کجا اضافه شود. از آنجایی که افزونه ما برای داده های برداری است، "Vector" را انتخاب کنید. کادر افزونه را به عنوان آزمایشی در پایین علامت گذاری کنید. روی :guilabel:`OK کلیک کنید.

../_images/2138.png
  1. در مرحله بعد، از شما خواسته می شود که یک دایرکتوری برای افزونه خود انتخاب کنید. شما باید به فهرست پلاگین QGIS python در رایانه خود بروید و Select Folder را انتخاب کنید. به طور معمول، دایرکتوری .qgis2/ در فهرست اصلی شما قرار دارد. مکان پوشه «پلاگین» به شرح زیر به پلتفرم شما بستگی دارد: (نام کاربری خود را جایگزین «نام کاربری» کنید)

پنجره ها

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

مک

/Users/username/.qgis2/python/plugins

لینوکس

/home/username/.qgis2/python/plugins
../_images/379.png
  1. پس از ایجاد الگوی افزونه شما یک گفتگوی تایید را مشاهده خواهید کرد. به مسیر پوشه افزونه توجه کنید.

../_images/447.png
  1. قبل از اینکه بتوانیم از افزونه جدید ایجاد شده استفاده کنیم، باید فایل "resources.qrc" را که توسط افزونه ساز ایجاد شده است، کامپایل کنیم. :guilabel:`OSGeo4W Shell را در ویندوز یا ترمینال در مک یا لینوکس راه اندازی کنید.

../_images/541.png
  1. به دایرکتوری افزونه که خروجی «Plugin Builder» در آن ایجاد شده است، بروید. می‌توانید از دستور «cd» و سپس مسیر دایرکتوری استفاده کنید.

cd c:\Users\username\.qgis2\python\plugins\SaveAttributes
../_images/640.png
  1. هنگامی که وارد دایرکتوری شدید، "make" را تایپ کنید. این دستور "pyrcc4" را که ما به عنوان بخشی از پیوندهای Qt برای پایتون نصب کرده بودیم اجرا می کند.

make
../_images/740.png
  1. Now we are ready to have a first look at the brand new plugin we created. Close QGIS and launch it again. Go to Plugins ‣ Manage and Install plugins and enable the Save Attributes plugin in the Installed tab. You will notice that there is a new icon in the toolbar and a new menu entry under Vector ‣ Save Attributes ‣ Save Attributes as CSV`. Select it to launch the plugin dialog.

../_images/839.png
  1. You will notice a new blank dialog named Save Attributes. Close this dialog.

../_images/940.png
  1. We will now design our dialog box and add some user interface elements to it. Open the Qt Creator program and to to File --> Open File or Project....

../_images/1047.png
  1. Browse to the plugin directory and select the save_attributes_dialog_base.ui file. Click Open.

../_images/1169.png
  1. You will see the blank dialog from the plugin. You can drag-and-drop elements from the left-hand panel on the dialog. We will add a Combo Box type of Input Widget. Drag it to the plugin dialog.

../_images/1250.png
  1. Resize the combo box and adjust its size. Now drag a Label type Display Widget on the dialog.

../_images/1348.png
  1. Click on the label text and enter Select a layer.

../_images/1445.png
  1. Save this file by going to File ‣ Save save_attributes_dialog_base.ui. Note the name of the combo box object is comboBox. To interact with this object using python code, we will have to refer to it by this name.

../_images/1541.png
  1. Let's reload our plugin so we can see the changes in the dialog window. Go to Plugin ‣ Plugin Reloader ‣ Choose a plugin to be reloaded.

../_images/1639.png
  1. Select SaveAttributes in the Configure Plugin reloader dialog.

../_images/1737.png
  1. Now click the Save Attributes as CSV button. You will see the newly designed dialog box.

../_images/1834.png
  1. Let's add some logic to the plugin that will populate the combo box with the layers loaded in QGIS. Go to the plugin directory and load the file save_attributes.py in a text editor. Scroll down and find the run(self) method. This method will be called when you click the toolbar button or select the plugin menu item. Add the following code at the beginning of the method. This code gets the layers loaded in QGIS and adds it to the comboBox object from the plugin dialog.

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. Back in the main QGIS window, reload the plugin by going to Plugins ‣ Plugin Reloader ‣ Reload plugin: SaveAttributes. Alternatively, you can just press F5. To test this new functionality, we must load some layers in QGIS. After you load some data, launch the plugin by going to Vector ‣ Save Attributes ‣ Save Attributes as CSV.

../_images/2022.png
  1. You will see that our combo box is now populated with the layer names that are loaded in QGIS.

../_images/2139.png
  1. Let's add remaining user interface elements. Switch back to Qt Creator and load the save_attributes_dialog_base.ui file. Add a Label Display Widget and change the text to Select output file. Add a LineEdit type Input Widget that will show the output file path that the user has chosen. Next, add a Push Button type Button and change the button label to .... Note the object names of the widgets that we will have to use to interact with them. Save the file.

../_images/2224.png
  1. We will now add python code to open a file browser when the user clicks the ... push button and show the select path in the line edit widget. Open the save_attributes.py file in a text editor. Add QFileDialog to our list of imports at the top of the file.

../_images/2321.png
  1. Add a new method called select_output_file with the following code. This code will open a file browser and populate the line edit widget with the path of the file that the user chose.

def select_output_file(self):
    filename = QFileDialog.getSaveFileName(self.dlg, "Select output file ","", '*.txt')
    self.dlg.lineEdit.setText(filename)
../_images/2421.png
  1. Now we need to add code so that when the ... button is clicked, select_output_file method is called. Scroll up to the __init__ method and add the following lines at the bottom. This code will clear the previously loaded text (if any) in the line edit widget and also connect the select_output_file method to the clicked signal of the push button widget.

self.dlg.lineEdit.clear()
self.dlg.pushButton.clicked.connect(self.select_output_file)
../_images/2520.png
  1. Back in QGIS, reload the plugin and open the Save Attributes` dialog. If all went fine, you will be able to click the ... button and select an output text file from your disk.

../_images/2618.png
  1. When you click OK on the plugin dialog, nothing happens. That is because we have not added the logic to pull attribute information from the layer and write it to the text file. We now have all the pieces in place to do just that. Find the place in the run method where it says pass. Replace it with the code below. The explanation for this code can be found in شروع برنامه نویسی پایتون.

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. Now our plugin is ready. Reload the plugin and try it out. You will find that the output text file you chose will have the attributes from the vector layer. You can zip the plugin directory and share it with your users. They can unzip the contents to their plugin directory and try out your plugin. If this was a real plugin, you would upload it to the QGIS Plugin Repository so that all QGIS users will be able to find and download your plugin.

توجه

This plugin is for demonstration purpose only. Do not publish this plugin or upload it to the QGIS plugin repository.

Below is the full save_attributes.py file as a reference.

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