Archive

Tag Archives: plugin development

This post shows how to quickly and easily create a small QGIS plugin for counting the number of features within a vector layer.

To get started, you will need QGIS and Qt Designer (to design the user interface) installed. If you are on Windows, I suggest WinPython which provides Qt Designer and Spyder (a Python IDE).

The great thing about creating plugins for QGIS: There is a plugin for that! It’s called Plugin Builder. And while you are at it, also install Plugin Reloader. Reloader is very useful for plugin developers because it lets you quickly reload your plugin without having to restart QGIS every time you make changes to the code.

installPluginBuilder

Plugin Builder will create all the files we need for our plugin. Just start it and select a name for your plugin class (one word in CamelCase), as well as a name for the plugin itself and the plugin menu entry (can be multiple words). Once you press Ok, you’re asked to select a folder to store the plugin. You can save directly to the QGIS plugin folder ~\.qgis2\python\plugins.

pluginBuilder

Next, open the newly created folder (in my case ~\.qgis2\python\plugins\BuilderTest). Amongst other files, it contains the user interface file ui_buildertest.ui. Our plugin will count the number of features in a vector layer. Therefore, it needs a combobox which allows the user to select a layer. Open the .ui file in Qt Designer and add a combobox to the dialog. Change the object name of the combobox to layerCombo. We’ll later use this name in the plugin code to add items to the combobox. Save the dialog and close Qt Designer.

qtDesigner

Now, we need to compile the .ui and the resources.qrc file to turn the dialog and the icon into usable Python code. This is done on the command line. On Windows, I suggest using the OSGeo4W Shell. Navigate to the plugin folder and run:

pyuic4 -o ui_buildertest.py ui_buildertest.ui
pyrcc4 -o resources_rc.py resources.qrc

If you enable and run the plugin now, you will already see the dialog but the combobox will be empty. To populate the combobox, we need to write a few lines of code in buildertest.py. First, we’ll fetch all loaded layers and add all vector layers to the combobox. Then, we’ll add code to compute and display the number of features in the selected layer. To achieve this, we expand the run() method:

def run(self):        
    # show the dialog
    self.dlg.show()

    layers = QgsMapLayerRegistry.instance().mapLayers().values()
    for layer in layers:
        if layer.type() == QgsMapLayer.VectorLayer:
            self.dlg.layerCombo.addItem( layer.name(), layer ) 
         
    # Run the dialog event loop
    result = self.dlg.exec_()
    # See if OK was pressed
    if result == 1:
        # do something useful 
        index = self.dlg.layerCombo.currentIndex()
        layer = self.dlg.layerCombo.itemData(index)
        QMessageBox.information(self.iface.mainWindow(),"hello world","%s has %d features." %(layer.name(),layer.featureCount()))

When you are done with the code, you can use Plugin Reloader to load the new version. When you start the plugin now, the combobox will be populated with the names of the vector layers in your current project. And on pressing Ok, the plugin will compute and display the number of features.

builderTEst

builderTestResult

For more information on PyQGIS and more code samples I warmly recommend the PyQGIS Cookbook. Have fun!

About these ads

As I’m sure you have already heard, QGIS 2.0 will come with a new Python API including many improvements and a generally more pythonic way of doing things. But of course that comes with a price: Old plugins (pre 2.0) won’t work anymore unless they are updated to the new version. Therefore all plugin developers are encouraged to take the time and update their plugins. An overview of changes and howto for updates is available on the QGIS wiki.

TimeManager for QGIS 2.0 will be available from day 1 of the new release. I’ve tested the usual work flows but don’t hesitate to let me know if you find any problems. The whole update process took two to three hours … sooo many signals to update … but all in all, it was far less painful than expected, thanks to everyone who contributed to the wiki update instructions!

  1. Since the release of QGIS 1.8, Plugin Installer no longer includes the “add 3rd party repositories” button. This was an intentional design choice!
  2. The new official plugin repository at plugins.qgis.org keeps everything in one place making it easier for users to find documentation and report issues. It will also provide many long-wanted features such as a rating system for plugins. You can already sort by number of downloads to discover the most popular plugins.
  3. Last but not least: New users will not be able to discover your plugin if it is not in the repository.

Go ahead to plugins.qgis.org and upload your plugin now!

Starting from scratch can be painful. Luckily there’s a tool out there that can help: PyQGIS Plugin Builder. The form will build a minimal plugin for you. You’ll get a ready QGIS 1.0 plugin that implements an empty dialog with Ok and Close buttons. You can find a step-by-step description on how to create and modify this dummy plugin at linfiniti.com – “A simple QGIS python tutorial”.

From there, I suggest moving to the PyQGIS Cookbook – a great resource for everything related to QGIS with Python – especially the part about “Developing Python Plugins”. Tim also created a PDF version of the Cookbook (original post) if you prefer it in hard copy.

The following code snippet contains the constructor of a GUI control class for QGIS plugins. It’s job is to initialize the plugin GUI (a dock widget in this case) and create signal connections:

import os
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import uic
from qgis.core import *

class MyGuiControl(QObject):
    """This class controls all plugin-related GUI elements."""

    def __init__ (self,iface):
        """initialize the GUI control"""
        QObject.__init__(self)
        self.iface = iface

        # load the form
        path = os.path.dirname( os.path.abspath( __file__ ) )
        self.dock = uic.loadUi( os.path.join( path, "mywidget.ui" ) )
        self.iface.addDockWidget( Qt.RightDockWidgetArea, self.dock )

        # connect to gui signals
        QObject.connect(self.dock...
        ...

Using uic.loadUi is a convenient way to create a user interface with minimum effort. Of course the performance might be better if we compile the .ui code to python first, but during development phase this saves us an extra step.

For some plugins it’s necessary to save the settings inside the QGIS project file. Saving is done with this simple one-liner:

QgsProject.instance().writeEntry(pluginName, setting, value)

Then you just need to save the project.

Reading is performed with one of the following functions:

QgsProject.instance().readEntry (pluginName, setting) # for strings
QgsProject.instance().readNumEntry (pluginName, setting)
QgsProject.instance().readDoubleEntry (pluginName, setting)
QgsProject.instance().readBoolEntry (pluginName, setting)
QgsProject.instance().readListEntry (pluginName, setting)

You’ll find the corresponding API documentation at: http://doc.qgis.org/stable/classQgsProject.html. As you can see, you can only read/write simple data types. To allow the plugin developer to save more complex plugin settings, I filed an enhancement request.

To handle all those different read functions in a convenient way, I created the following functions:

def readSetting(self,func,setting):
    """read a plugin setting from QgsProject instance"""
    value,ok = func('pluginName',setting)
    if ok:
        return value
    else:
        return None
            
def readSettings(self,setting,value):
    """read plugin settings from QgsProject instance"""
    # map data types to function names
    prj = QgsProject.instance()
    functions = { 'str' : prj.readEntry,
                  'int' : prj.readNumEntry,
                  'float' : prj.readDoubleEntry,
                  'bool' : prj.readBoolEntry,
                  'pyqtWrapperType' : prj.readListEntry # QStringList
                }
        
    dataType = type(value).__name__
    return = self.readSetting(functions[dataType],setting)

readSettings() has to be supplied with the name of the setting and an example or default value for the setting (from which we can determine the data type of the setting). Of course this can be done in many different ways. In Time Manager plugin, readSettings() receives a dictionary of all settings that have to be read. The function then loops through the dictionary and reads the available settings.

Follow

Get every new post delivered to your Inbox.

Join 2,959 other followers

%d bloggers like this: