以 Python 制作附加元件¶
警告
本教学已有新的版本,请前往 以 Python 制作附加元件 (QGIS3)。
增进 QGIS 功能的最佳方法,就是使用附加元件。你也可以使用 Python 来写一个,从只有一个按钮到复杂的功能面板,都可任君挑选。本教学会介绍设计附加元件的大致流程,包括设置开发环境、打造使用者介面,以及撰写程式码与 QGIS 互动。有关较为基础的部分,请参阅 Python 程式设计的初步上手。
内容说明¶
我们要开发一个简单的附加元件,称为 Save Attributes
,它可以让使用者任意挑选一个向量图层,把它的属性另存为 CSV 档。
取得工具¶
Qt Creator¶
Qt 是一套软体开发框架,用于设计在 Windows、Mac、Linux 或是其他行动作业系统上执行的软体。QGIS 本身就是用 Qt 框架打造的,所以我们在这里要使用一个称为 Qt Creator 的程式来设计我们附加元件的介面。
从 SourgeForge 上下载并安装 Qt Creator。
Qt 的 Python Bindings¶
由于我们要使用 Python 来设计附加元件,因此得安装 Qt 的 Python binding ,以便在 Python 中可以轻松使用 Qt 的功能。此步骤会因作业系统的不同而不同,像是 Windows 命令列下,要装的程式叫做 pyrcc4
。
Windows
下载 OSGeo4W network installer 然后选择 Express Desktop Install,选择安装 QGIS
的套件。安装完毕后,你就可以经由 OSGeo4W Shell 存取 pyrcc4
工具。
Mac
安装 Homebrew 套件管理员,然后使用以下指令安装 PyQt
套件:
brew install pyqt
Linux
在你的发行版中寻找并安装 python-qt4
套件。在 Ubuntu 和其他基于 Debian 的发行版中,可以使用如下指令:
sudo apt-get install python-qt4
编辑器或 Python IDE¶
要进行任何种类的软体开发,优良的文字编辑器是必不可少的。在本教学中,你可以使用你喜欢的文字编辑器或 IDE (整合开发环境);如果没有的话,每个作业系统都有很多免费或付费的文字编辑软体可使用,挑一个符合你需求的即可。
本教学中使用的是 Windows 版本的 Notepad++ 编辑器。
Windows
Notepad++ 是一款好用且免费的编辑器,可安装于 Windows 下。下载并安装 Notepad++ editor
注解
如果你使用的是 Notepad++,请确认你有在 Replace by space。Python 对于空白缩排设定非常敏感,此选项可以确保你使用 tab 和 space 键制造的空白可以被适当的设定。
的地方勾选操作流程¶
开启 QGIS,选择
。
QGIS Plugin Builder 视窗会与资料表格一起出现,你可以在此处把我们要制作的附加元件的相关资讯填在表中。Class name 是本附加元件使用的主要 Python 类别,同时也是储存所有附加元件档案的资料夹名称,请在此输入
SaveAttributes
。 Plugin name 是你的附加元件会在 Plugin Manager 中显示的名称,请输入Save Attributes
。Description 栏位中可添加一些相关描述,而 Module name 则是附加元件主要存取的 Python 档案名称,请输入save_attributes
。版本号码可先维持预设,Text for menu item 则与附加元件在 QGIS 选单中显示的文字有关。Menu 栏位则是让你决定外挂元件会放在选单列中的哪个分类(译按:新版已移除此栏位)。由于我们的附加元件是针对向量资料,这里请选Vector
。勾选底部的 Flag the plugin as experimental,然后按下 OK 或 NEXT(译按:新版的 QGIS 把此表格分成几个页面,所以你可能要按下几次 NEXT 才可见到所有选项)。
接下来你要为附加元件指定储存的路径。你需要前往你电脑内的 QGIS python 附加元件路径,然后按下 选择资料夹。在一般的状况下,
.qgis2/
资料夹会在你的家目录底下,然后plugin
资料夹的路径则会根据作业系统的不同而不同,如下所示:(请把username
换成你的使用者名称)
Windows
c:\Users\username\.qgis2\python\plugins
Mac
/Users/username/.qgis2/python/plugins
Linux
/home/username/.qgis2/python/plugins
附加元件的模板建立后,会有个确认视窗出现。请注意存放附加元件的路径。
在我们可以使用新创造的附加元件之前,必须要先编译由 Plugin Builder 产生的
resources.qrc
档案。请在 windows 上开启 OSGeo4W Shell,或在 Mac 或 Linux 上开启终端机。
你可以透过
cd
指令,接续资料夹的路径名称,前往Plugin Builder
输出的附加元件档案存放的资料夹。
cd c:\Users\username\.qgis2\python\plugins\SaveAttributes
在此目录之下输入
make
,之前安装的 Python 的 Qt bindings 中的pyrcc4
指令就会执行。
make
现在我们已经准备完毕,来看看我们刚才创造的附加元件吧。关闭 QGIS 后重新启动,然后前往 已安装 分页中启用
,在Save Attributes
。然后你会发现在工具列的以下路径出现了,而且还有新图示: 。点选后可开启附加元件的视窗。
你会看到一个叫做 Save Attributes 的视窗出现。可以关掉了。
我们现在要开始设计我们的视窗,并在上面添加一些新元素。开启
Qt Creator
程式,选择 档案 –> 开启档案或专案…
前往附加元件的资料夹,选择档案
save_attributes_dialog_base.ui
,然后按 开启。
附加元件的空白视窗就会在这里出现。你可以从左边的面板中拖曳加入视窗中的一些元件,这里我们要加上 Input Widget 中的 Combo Box(组合框),把它拖曳到附加元件的视窗中。
调整组合框的大小,然后再从 Display Widget 中拖曳一个 Label(标籤)到视窗上。
点选标籤的文字然后输入
Select a layer
。
选择
,以储存档案。注意组合框目前的物件名称为comboBox
,如果要使用 Python 操作物件,我们需要记住物件的名称才行。
QGIS 重新载入附加元件之后,我们就能在视窗中看到刚才做的改变。前往
在 Configure Plugin reloader 视窗中选择
SaveAttributes
。
现在点选 Save Attributes as CSV 按钮后,你就会看到新设计的视窗。
让我们来增加一点东西到组合框中,使 QGIS 载入的图层能列出来。前往附加元件资料夹,使用文字编辑器开启
save_attributes.py
,下拉至run(self)
的方法,这个方法会在从工具列按钮或选单点选附加元件时。在此方法的开头添加以下的程式码,它会取得 QGIS 中载入的图层然后把它们加到附加元件视窗中的comboBox
物件中。
layers = self.iface.legendInterface().layers()
layer_list = []
for layer in layers:
layer_list.append(layer.name())
self.dlg.comboBox.addItems(layer_list)
回到 QGIS 主视窗,然后选择
以再次重新启动附加元件;或是按下 F5 也可以达到相同目的。为了测试刚才新加的功能,我们要在 QGIS 中载入一些图层。载入之后,选择 以开启此附加元件。
现在你可以看到我们的组合框出现了 QGIS 中载入图层的名字了。
让我们把剩下的使用者介面元素也添加进来。回到
Qt Creator
然后载入save_attributes_dialog_base.ui
,再从 Display Widget 加入一个Label
,然后文字改为Select output file
,接着从 Input Widget 加入LineEdit
,他会秀出使用者选择的输出档档名;再来从 Button 加入一个Push Button
(按钮),然后把按钮的标籤改为...
。记住这些物件的名字,我们要使用它们与物件本体互动。最后请存档。
现在要做的是加上一段 Python 程式码,让使用者在按下
...
钮的时候,会开启一个新视窗选择档案路径,并且把此路径显示在 Line Edit 框内。以文字编辑器打开save_attributes.py
,在档案开头部分、汇入模组的清单中加上QFileDialog
。
加入名为
select_output_file
的新方法,内容如下程式码所示。此程式码会开启档案浏览器,并且在 Line Edit 框位中贴上使用者选择的档案路径。
def select_output_file(self):
filename = QFileDialog.getSaveFileName(self.dlg, "Select output file ","", '*.txt')
self.dlg.lineEdit.setText(filename)
现在我们要加上「当按下 … 钮时,就启动
select_output_file
方法」的程式码。上移至__init__
方法,然后在底部加上如下几行,它们的作用是清除之前在 Line Edit 框中遗留下来的任何文字 (如果有的话),然后把按钮的点选
讯号与select_output_file
方法连结起来。
self.dlg.lineEdit.clear()
self.dlg.pushButton.clicked.connect(self.select_output_file)
回到 QGIS 中,重新载入附加元件,然后开启 Save Attributes` 使窗。如果一切正常,你就可以按下
...
钮,然后从磁碟中选择输出档档案。
当按下 OK 时,什么事都不会发生。这是因为我们还没有加上把属性的资讯转存到文字档内的城市部分。我们现在已经有所需的所有元素来做到这件事了,请前往
run
的方法,其中会看到一个pass
,再把它以如下的程式码取代。这段程式码的解释可在 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()
现在附加元件已完成,重新载入后就来试试看吧,你会发现输出的文字档会含有向量图层中的属性资讯。附加元件的资料夹可以压缩后与其他使用者分享,只要重新解压缩到他们的附加元件资料夹,就可以开始使用。你也可以上传到官方的 QGIS 附加元件储存库,这样所有的 QGIS 使用者都能找到并下载你的附加元件。
注解
本附加元件仅供示范使用,请勿任意出版或上传至 QGIS 附加元件储存库。
以下放上完整的 save_attributes.py
档做为参考。
# -*- 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()