Xây dựng Plugin với Python (QGIS3)

Tổng quan về nhiệm vụ

Chúng ta sẽ phát triển một plugin đơn giản có tên Save Attributes cho phép người dùng chọn một lớp vectơ và ghi các thuộc tính của nó vào tệp CSV.

Tải và cài đặt công cụ

Qt Creator

Qt là một khung phát triển phần mềm được sử dụng để phát triển các ứng dụng chạy trên Windows, Mac, Linux cũng như các hệ điều hành di động khác nhau. Bản thân QGIS được viết bằng Qt framework. Để phát triển plugin, chúng tôi sẽ sử dụng một ứng dụng có tên Qt Creator để thiết kế giao diện cho plugin của chúng ta.

Tải và cài đặt Qt Creator từ Qt Offline Installers. Đảm bảo bạn chọn Qt Creator trên trang tải xuống. Lưu ý rằng bạn sẽ phải tạo một tài khoản Qt miễn phí để cài đặt gói.

Ghi chú:
Trình cài đặt OSGeo4w cho QGIS trên Windows bao gồm một bản sao của chương trình Qt Designer, đây là phiên bản nhẹ của Qt Creator và hoàn toàn phù hợp để xây dựng các plugin. Bạn có thể bỏ qua việc tải xuống Qt Creator và sử dụng nó thay thế từ C:\OSGeo4W64\bin\qgis-designer.

Python Bindings for Qt

Vì chúng ta đang phát triển plugin trong Python, chúng tôi cần cài đặt các liên kết python cho Qt. Phương pháp cài đặt này sẽ phụ thuộc vào nền tảng bạn đang sử dụng. Để xây dựng các plugin chúng ta cần công cụ dòng lệnh pyrcc5.

Windows
Các ràng buộc pyhon có liên quan được bao gồm trong cài đặt QGIS trên Windows. Nhưng để sử dụng chúng từ thư mục plugin, chúng ta cần chỉ ra đường dẫn đến bản cài đặt QGIS.
ạo tệp Windows Batch (phần mở rộng .bat) với nội dung sau và lưu nó trên máy tính của bạn dưới dạng compile.bat. Sau này chúng tôi sẽ sao chép tệp này vào thư mục plugin. Nếu bạn đã cài đặt QGIS ở một đường dẫn khác, hãy thay thế C:\OSGeo4W64\bin\ bằng đường dẫn của bạn.

@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
Cài đặt trình quản lý gói Homebrew. Cài đặt gói PyQt bằng cách chạy lệnh sau:

brew install pyqt

Linux
ùy thuộc vào phiên bản, hãy tìm và cài đặt gói python-qt5. Trên các bản phân phối dựa trên Ubuntu và Debian, bạn có thể chạy lệnh sau:

sudo apt-get install python-qt5

Ghi chú:
Bạn có thể thấy rằng QGIS đã cài đặt gói này.

Trình soạn thảo văn bản (Text Editor) hoặc IDE Python
Bất kỳ loại phát triển phần mềm đòi hỏi một trình soạn thảo văn bản tốt. Nếu bạn đã có trình soạn thảo văn bản yêu thích hoặc IDE (Integrated Development Environment – Môi trường phát triển tích hợp), bạn có thể sử dụng nó cho hướng dẫn này. Mặt khác, mỗi nền tảng cung cấp nhiều tùy chọn miễn phí hoặc trả phí cho người chỉnh sửa văn bản. Chọn một trong những phù hợp với nhu cầu của bạn.

Hướng dẫn này sử dụng trình soạn thảo Notepad ++ trên Windows.

Windows
Notepad++ là một trình soạn thảo miễn phí tốt cho windows. Tải xuống và cài đặt trình soạn thảo Notepad++.

Plugin Builder plugin
Có một plugin QGIS hữu ích có tên Plugin Builder tạo ra tất cả các tệp cần thiết và mã soạn sẵn cho plugin. Tìm và cài đặt plugin Plugin Builder. Xem Sử dụng Plugin để biết thêm chi tiết về cách cài đặt plugin.

Plugins Reloader plugin
Đây là một plugin trợ giúp khác cho phép phát triển các plugin lặp đi lặp lại. Sử dụng plugin này, bạn có thể thay đổi mã plugin của mình và để nó được phản ánh trong QGIS mà không phải khởi động lại QGIS mỗi lần. Tìm và cài đặt Plugin Reloader. Xem Sử dụng Plugin để biết thêm chi tiết về cách cài đặt plugin.

Ghi chú:
Plugin Reloader là một plugin thử nghiệm. Đảm bảo bạn đã kiểm tra Hiển thị các plugin thử nghiệm trong cài đặt Plugin Manager nếu bạn không thể tìm thấy nó.

Procedure
1. Mở QGIS. Chọn Plugins ‣ Plugin Builder ‣ Plugin Builder.

2. Bạn sẽ thấy hộp thoại QGIS Plugin Builder. Bạn có thể điền vào với các chi tiết liên quan đến plugin của chúng ta. Tên Class sẽ là tên của Lớp Python chứa logic của plugin. Đây cũng sẽ là tên của thư mục chứa tất cả các tệp plugin. Nhập SaveAttribution làm tên lớp. Tên Plugin là tên mà theo đó plugin của bạn sẽ xuất hiện trong Trình quản lý plugin. Nhập tên là SaveAttributes. Thêm một mô tả trong trường Description. Module name sẽ là tên của tệp python chính cho plugin. Nhập vào save_attribut. Để lại số phiên bản như hiện tại và nhập tên và địa chỉ email của bạn vào các trường thích hợp. Nhấn Next.

3. Nhập mô tả ngắn gọn về plugin cho hộp thoại About và nhấp vào Next.

4. Chọn nút Tool button with dialog từ bộ chọn Template. Giá trị mục văn bản cho menu sẽ là cách người dùng sẽ tìm thấy plugin của bạn trong menu QGIS. Nhập vào Save Attributes as CSV. Trường Menu sẽ quyết định vị trí mục plugin của bạn được thêm vào trong QGIS. Vì plugin của chúng ta dành cho dữ liệu vectơ, hãy chọn Vector. Nhấn Next.

5. Trình tạo plugin sẽ nhắc bạn về loại tệp cần tạo. Giữ lựa chọn mặc định và nhấn Next.

6. Vì chúng ta không có ý định xuất bản plugin, bạn có thể để các giá trị Bug tracker, Repository và Home page là mặc định. Check chọn Flag the plugin as experimental ở phía dưới và nhấp vào Next.

7. Bạn sẽ được nhắc chọn một thư mục cho plugin của bạn. Hiện tại, hãy lưu nó vào một thư mục mà bạn có thể dễ dàng xác định vị trí trên máy tính của mình và nhấp vào Generate.

8. Tiếp theo, nhấn nút generate. Bạn sẽ thấy một hộp thoại xác nhận khi mẫu plugin của bạn được tạo.

Ghi chú:
Bạn có thể nhận được lời nhắc rằng pyrcc5 không được tìm thấy trong đường dẫn. Bạn có thể bỏ qua thông báo này.

9. Trước khi có thể sử dụng plugin mới được tạo, cần biên dịch tệp resource.qrc được tạo bởi Plugin Builder. Tệp này là một phần của Hệ thống tài nguyên Qt tham chiếu tất cả các tệp nhị phân được sử dụng trong plugin. Đối với plugin này, nó sẽ chỉ có biểu tượng plugin. Biên dịch tệp này tạo mã ứng dụng có thể được sử dụng trong plugin độc lập với nền tảng mà plugin đang được chạy. Thực hiện theo các hướng dẫn cụ thể nền tảng cho bước này.

Windows
Bây giờ bạn có thể sao chép tệp compile.bat (được tạo trong phần Python Bindings cho Qt khi bắt đầu) vào thư mục plugin. Sau khi sao chép, bấm đúp vào tệp để chạy nó. Nếu quá trình chạy thành công, bạn sẽ thấy một tệp mới có tên là resource.py trong thư mục.

Ghi chú:
Nếu bước này không thành công, bạn có thể khởi chạy cmd.exe và duyệt đến thư mục plugin bằng lệnh cd. Chạy tệp Batch bằng cách chạy compile.bat để xem lỗi.

Mac và Linux
Bạn sẽ cần cài đặt pb_tool trước. Mở Terminal và cài đặt nó qua pip.

sudo pip3 install pb_tool

Mở Terminal và vào thư mục plugin và gõ pb_tool compile. Điều này sẽ chạy lệnh pyrcc5 mà chúng ta đã cài đặt như một phần Python Bindings for Qt.

pb_tool compile

10. Các plugin trong QGIS được lưu trữ trong một thư mục đặc biệt. Cần phải sao chép thư mục plugin của chúng ta vào thư mục đó trước khi có thể sử dụng. Trong QGIS, định vị thư mục hồ sơ hiện tại của bạn bằng cách chọn Settings ‣ User Profiles ‣ Open Active Profile Folder.

11. Trong thư mục profile, sao chép thư mục plugin vào thư mục con python > plugin.

12. Bây giờ chúng tôi đã sẵn sàng để có cái nhìn đầu tiên về plugin hoàn toàn mới mà chúng tôi đã tạo. Đóng QGIS và khởi chạy lại. Chuyển đến plugin Save Attributes và bật plugin Save Attributes trong tab Installed.

13. Bạn sẽ nhận thấy rằng có một biểu tượng mới trong thanh công cụ plugin và mục nhập menu mới trong Vector > Save Attributes > Save Attributes as CSV. Chọn nó để khởi chạy hộp thoại plugin.

14. Bạn sẽ nhận thấy một hộp thoại trống mới có tên Save Attributes. Đóng hộp thoại này.

Bây giờ sẽ thiết kế hộp thoại của chúng tôi và thêm một số yếu tố giao diện người dùng vào nó. Mở chương trình Qt Creator và đi đến File > Open File or Project.

Duyệt đến thư mục plugin và chọn tệp save_attribut_dialog_base.ui. Bấm Open.

Gih chú:
Windows ẩn thư mục AppData để bạn có thể không nhìn thấy nó trong hộp thoại chọn tệp. Bạn có thể nhập AppData trong lời nhắc tên tệp từ thư mục mẹ của nó để mở nó.

17. Bạn sẽ thấy hộp thoại trống từ plugin. Bạn có thể kéo và thả các phần tử từ bảng điều khiển bên trái trên hộp thoại. Chúng ta sẽ thêm một loại Combo Box của Widgets đầu vào. Kéo nó vào hộp thoại plugin.

18. Thay đổi kích thước combobox và điều chỉnh kích thước của nó. Bây giờ kéo một Label trong Display Widget trên hộp thoại.

19. Click vào label text và nhập Select a layer.

20. Ghi lại nằng cách chọn File > Save save_attributes_dialog_base.ui. Lưu ý tên của đối tượng combobox là comboBox. Để tương tác với đối tượng này bằng mã python, chúng ta sẽ phải gọi nó bằng tên này.

21. Hãy để tải lại plugin để chúng ta có thể thấy những thay đổi trong cửa sổ hộp thoại. Chuyển đến Plugin > Plugin Reloader > Choose để được tải lại. Chọn SaveAttribution trong hộp thoại Configure Plugin reloader.

22. Nhấp vào nút Reloadplugin để tải phiên bản mới nhất của plugin. Nhấp vào Save Attributes as CSV để mở hộp thoại được thiết kế mới.

Hãy để thêm một số logic vào plugin sẽ điền vào hộp tổ hợp với các lớp được tải trong QGIS. Chuyển đến thư mục plugin và tải tệp save_attribut.py trong trình chỉnh sửa văn bản. Đầu tiên, chèn vào đầu tệp với các lần nhập khác:

from qgis.core import QgsProject

Sau đó cuộn xuống cuối và tìm phương thức run (self). Phương pháp này sẽ được gọi khi bạn nhấp vào nút thanh công cụ hoặc chọn mục menu plugin. Thêm mã sau vào đầu phương thức. Mã này nhận được các lớp được tải trong QGIS và thêm nó vào đối tượng comboBox từ hộp thoại plugin.

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

24. Quay lại cửa sổ QGIS chính, tải lại plugin bằng cách nhấp vào nút Reload plugin. Để kiểm tra chức năng mới này, chúng tôi phải tải một số lớp trong QGIS. Sau khi bạn đã tải một số lớp, hãy khởi chạy plugin bằng cách truy cập Vector > Save Attributes > Save Attributes as CSV. Bạn sẽ thấy rằng hộp tổ hợp của chúng tôi hiện được điền với các tên lớp được tải trong QGIS.

25. Hãy để thêm các yếu tố giao diện người dùng còn lại. Quay trở lại Qt Creator và tải tệp save_attribut_dialog_base.ui. Thêm một Widget Hiển thị nhãn và thay đổi văn bản thành Chọn tệp đầu ra. Thêm một Widget đầu vào kiểu LineEdit sẽ hiển thị đường dẫn tệp đầu ra mà người dùng đã chọn. Tiếp theo, thêm Nút loại Nút ấn và thay đổi nhãn nút thành …. Lưu ý tên đối tượng của các tiện ích mà chúng ta sẽ phải sử dụng để tương tác với chúng. Lưu các tập tin.

26. Bây sẽ thêm mã python để mở trình duyệt tệp khi người dùng nhấp vào nút … và hiển thị đường dẫn chọn trong tiện ích chỉnh sửa dòng. Mở tệp save_attribut.py trong trình soạn thảo văn bản. Thêm QFileDialog vào danh sách nhập của QtWidgets ở đầu tệp.

27. Thêm một phương thức mới gọi là selectDefput_file với đoạn mã sau. Mã này sẽ mở trình duyệt tệp và điền vào tiện ích chỉnh sửa dòng với đường dẫn của tệp mà người dùng đã chọn. Lưu ý, làm thế nào getSaveFileName trả về một tuple với tên tệp và bộ lọc được sử dụng.

def select_output_file(self):
  filename, _filter = QFileDialog.getSaveFileName(
    self.dlg, "Select   output file ","", '*.csv')
  self.dlg.lineEdit.setText(filename)

28. Bây giờ chúng ta cần thêm mã để khi nhấp vào nút …, phương thức selectDefput_file được gọi. Cuộn xuống phương thức chạy và thêm dòng sau vào khối nơi hộp thoại được khởi tạo. Mã này sẽ kết nối phương thức selectDefput_file với tín hiệu được nhấp của tiện ích nút ấn.

self.dlg.pushButton.clicked.connect(self.select_output_file)

29. Quay lại QGIS, tải lại plugin và chạy nó. Nếu mọi việc suôn sẻ, bạn sẽ có thể nhấp vào nút … và chọn tệp văn bản đầu ra từ đĩa của mình.

30. Khi bạn bấm OK trên hộp thoại plugin, không có gì xảy ra. Đó là bởi vì chúng tôi chưa thêm logic để lấy thông tin thuộc tính từ lớp và ghi nó vào tệp văn bản. Bây giờ chúng ta có tất cả các phần tại chỗ để làm điều đó. Tìm vị trí phương thức run nơi có thông báo pass. Thay thế nó bằng mã dưới đây. Có thể tìm thấy lời giải thích cho mã này trong Getting Started With Python Programming (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)
  # wirte feature attributes
  for f in selectedLayer.getFeatures():
    line = ','.join(str(f[name]) for name in fieldnames) + '\n'
    output_file.write(line)

31. Chúng ta có một điều cuối cùng để thêm. Khi hoạt động kết thúc thành công, chúng ta nên chỉ ra điều tương tự cho người dùng. Cách ưa thích để đưa ra thông báo cho người dùng trong QGIS là thông qua phương thức self.iface.messageBar (). PushMessage (). Thêm Qgis vào danh sách nhập của qgis.core ở đầu tệp và thêm mã bên dưới vào cuối phương thức chạy.

self.iface.messageBar().pushMessage(
  "Success", "Output file written at " + filename,
  level=Qgis.Success, duration=3)

32. Bây giờ plugin của chúng tôi đã sẵn sàng. Tải lại plugin và dùng thử. Bạn sẽ thấy rằng tệp văn bản đầu ra bạn chọn sẽ có các thuộc tính từ lớp vectơ.

33. Bạn có thể nén thư mục plugin và chia sẻ nó với người dùng của bạn. Họ có thể giải nén nội dung vào thư mục plugin của họ và thử plugin của bạn. Nếu đây là một plugin thực sự, bạn sẽ tải nó lên Kho lưu trữ Plugin của QGIS để tất cả người dùng QGIS có thể tìm và tải xuống plugin của bạn.

Dưới đây là nội dung toàn bộ tệp save_attribut.py

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

Bài viết gốc: Building a Python Plugin (QGIS3)

Bình luận bằng Facebook Comments