The PyQGIS Programmer's Guide
The PyQGIS Programmer's Guide
The PyQGIS Programmer's Guide
The
PyQGIS
Programmer’s Guide
Extending QGIS 2.x with Python
Credits & Copyright
T H E P Y QGIS P RO G R A M M E R ’ S G U I D E
E X T E N D I N G QGIS 2. X WITH PYTHON
by Gary Sherman
C O P Y R I G H T © 2016 L O C AT E P R E S S LLC
ISBN: 978-0989421720
A L L R I G H T S R E S E RV E D .
1 Introduction 11
1.1 Requirements . . . . . . . . . . . . . . . . . . . . . . . . 11
1.2 Sample Data . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.4 Conventions Used in this Book . . . . . . . . . . . . . . . 12
1.5 Your First PyQGIS experiment . . . . . . . . . . . . . . . 13
1.6 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2 Python Basics 17
2.1 Getting Help . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2 Data Structures . . . . . . . . . . . . . . . . . . . . . . . 18
2.3 Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.4 Strings, Ranges, and Other Handy Things . . . . . . . . . 25
2.5 Installing Packages . . . . . . . . . . . . . . . . . . . . . 29
2.6 Documenting Your Code . . . . . . . . . . . . . . . . . . 30
2.7 Resources to Learn More . . . . . . . . . . . . . . . . . . 32
2.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 33
7 Running Scripts 73
7.1 Standalone Scripts in the Console . . . . . . . . . . . . . 73
7.2 Running Scripts with Script Runner . . . . . . . . . . . . 79
7.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . 84
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
CONTENTS 5
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 CO N T E N T S
17 Index 193
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
List of Figures
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
Listings
2.1 simplepoint.py . . . . . . . . . . . . . . . . . . . . . . . 22
2.2 point3d_1.py . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3 point3d.py . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.4 point.py . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
7.1 first_script.py . . . . . . . . . . . . . . . . . . . . . . . . 74
7.2 no_methods.py . . . . . . . . . . . . . . . . . . . . . . . 77
7.3 first_script_class.py . . . . . . . . . . . . . . . . . . . . . 77
7.4 first_script_sr.py . . . . . . . . . . . . . . . . . . . . . . . 80
7.5 first_script_sr_documented.py . . . . . . . . . . . . . . . 82
9.1 wrapper.py: The Imports . . . . . . . . . . . . . . . . . . 119
9.2 wrapper.py: addLayer Function . . . . . . . . . . . . . . . 120
9.3 wrapper.py: addOgrLayer and addGdalLyer Functions . . 120
9.4 wrapper.py: Remove Layer . . . . . . . . . . . . . . . . . 124
9.5 wrapper.py: Using the addLayer Function . . . . . . . . . 124
9.6 wrapper.py: Changing Color and Transparency . . . . . . 125
9.7 wrapper.py: Changing Colors . . . . . . . . . . . . . . . . 126
9.8 wrapper.py: Complete Script . . . . . . . . . . . . . . . . 127
10.1 whereami.py: Imports and Init Method . . . . . . . . . . . 145
10.2 whereami.py: Adding the Map Tool . . . . . . . . . . . . 146
10.3 whereami.py: display_point Method . . . . . . . . . . . . 147
10.4 whereamidialog.py . . . . . . . . . . . . . . . . . . . . . 149
10.5 whereami.py: Modified display_point Method . . . . . . . 149
11.1 compile.py . . . . . . . . . . . . . . . . . . . . . . . . . 156
12.1 interactive.py . . . . . . . . . . . . . . . . . . . . . . . . 162
12.2 interactive_qgis.py . . . . . . . . . . . . . . . . . . . . . 164
12.3 ourmainwindow_1.py . . . . . . . . . . . . . . . . . . . . 166
12.4 our_app_1.py . . . . . . . . . . . . . . . . . . . . . . . . 167
10 LISTINGS
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1
Introduction
1.1 Requirements
To learn PyQGIS you need several pieces of software: You must use Python 2.x; QGIS does
not support Python 3.x.
• A working QGIS 2.0 install on Linux, Mac, or Windows
• Python 2.x
• Qt2
• PyQt3 2
http://loc8.cc/ppg/qt
3
http://qt-project.org
Before you dive in and start installing the components needed to work
with PyQGIS, take a look at Chapter 3, Setting Up Your Development
Tools, on page 35. You may already have everything you need to get started.
12 C H A P T E R 1 . I N T RO D U C T I O N
http://locatepress.com/ppg/data_code
For raster layers, download one of the Natural Earth rasters available at:
If you are familiar with vector and raster data, feel free to use your own and
adapt the commands/examples accordingly.
1.3 Code
All of the code examples in this book are available for download at:
http://locatepress.com/ppg/data_code
You are encouraged to work through the examples on your own, but feel
free to copy and paste.
This book contains code examples, interactive Python sessions, and notes/tips.
The convention for each of these is illustrated below:
Code listings:
Code listings are presented in a fixed width font and may or may not include
line numbers:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 . 5 . YO U R F I R S T P Y Q G I S E X P E R I M E N T 13
1 """
2 ScriptRunner is the main plugin class that initializes the QGIS
3 plugin, initializes the GUI, and performs the work.
4 """
5
6 def __init__(self, iface):
7 """
8 Save reference to the QGIS interface
9 """
10 self.iface = iface
Input lines in an interactive session (something you typed) are prefaced with
the >>> prompt. Indented lines in a block are preceded with ... and output
from the interpreter is shown without any leading characters:
>>> my_list = ['north', 'south', 'east', 'west']
>>> my_list[1]
'south'
>>> for d in my_list:
... print d
...
north
south
east
west
Notes/Tips:
Screenshots:
Screenshots throughout the book are a mix of those taken from Linux, Mac
OS X, and Windows versions of QGIS 2.0.
Open QGIS and load the world_borders.shp file using the Add Vector
Layer menu.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
14 C H A P T E R 1 . I N T RO D U C T I O N
For this experiment we’ll be using methods (think functions) defined by the
QgisInterface class:
• zoomFull()
• zoomToPrevious()
• zoomToNext()
• showAttributeTable()
• showLayerProperties()
The iface object gives you access When you open the console, there is a hint on getting help on using the
to a wide range of QGIS objects and
iface object. Using the built-in help function you can get a list of the
classes.
methods and functions defined in the QgisInterface class:
help(iface)
Help on QgisInterface in module qgis.gui object:
class QgisInterface(PyQt4.QtCore.QObject)
| QgisInterface()
|
| Method resolution order:
| QgisInterface
| PyQt4.QtCore.QObject
| sip.wrapper
| sip.simplewrapper
| __builtin__.object
|
| Methods defined here:
|
| composerAdded = <unbound signal composerAdded>
| composerWillBeRemoved = <unbound signal composerWillBeRemoved>
| currentLayerChanged = <unbound signal currentLayerChanged>
| initializationCompleted = <unbound signal initializationCompleted>
| newProjectCreated = <unbound signal newProjectCreated>
| projectRead = <unbound signal projectRead>
| ------------------------------------------------------------------
| Data and other attributes defined here:
|
| actionAbout = <built-in function actionAbout>
| actionAddAllToOverview = <built-in function actionAddAllToOverview>
| actionAddFeature = <built-in function actionAddFeature>
| actionAddOgrLayer = <built-in function actionAddOgrLayer>
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 . 5 . YO U R F I R S T P Y Q G I S E X P E R I M E N T 15
|
| ...
|
| zoomFull = <built-in function zoomFull>
| zoomToActiveLayer = <built-in function zoomToActiveLayer>
| zoomToNext = <built-in function zoomToNext>
| zoomToPrevious = <built-in function zoomToPrevious>
We only listed the first few and last four functions supported by iface.
Later we’ll show you another way to get at this information using the QGIS
online API documentation.
Let’s use iface to manipulate the view. With the mouse, zoom into any
area of the world you like. Now we will use the console to zoom back to
the full extent:
iface.zoomFull()
When you press enter, the map is zoomed back out to the full extent of the
world_borders layer.
Now try:
iface.zoomToPrevious()
This zooms us to the previous view, which happens to be the area you
zoomed to with the mouse. If you try iface.zoomToNext(), you’ll find
it gives the same result as iface.zoomFull().
To illustrate a couple of other methods, we’ll open the attribute table and the
layer properties dialog for the active layer. First we have to get a reference
to the active layer. Make sure the world_borders layer is highlighted in
the layer list, then in the console enter:
active_layer = iface.activeLayer()
Once we have a reference to the active layer, we can use the following to
open both its attribute table and the layer properties:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
16 C H A P T E R 1 . I N T RO D U C T I O N
iface.showAttributeTable(active_layer)
iface.showLayerProperties(active_layer)
>>> iface
<qgis.gui.QgisInterface object at 0x115d78170>
>>> iface.zoomFull()
>>> iface.zoomToPrevious()
>>> iface.zoomToNext()
>>> active_layer = iface.activeLayer()
>>> active_layer
<qgis.core.QgsVectorLayer object at 0x1116c4440>
>>> iface.showAttributeTable(active_layer)
>>> iface.showLayerProperties(active_layer)
If you look at the documentation for the QgisInterface class you’ll see it
has a lot more methods than those we just used. We’ll talk more about the
QGIS API in a later chapter. This tour of the console has given you a small
taste of what you can accomplish using PyQGIS. We’ll dive in further when
we get to Chapter 6, Using the Console, on page 61.
1.6 Exercises
To complete the exercises, you’ll need to look at the QGIS API documen-
tation found at http://loc8.cc/ppg/iface
2. Determine which method you would use to add a raster layer to the
map.
3. Adding a shapefile to the map requires the full path to the layer, a
short name (basename), and a provider key (’ogr’). Using the console,
add the world_borders shapefile to the map.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2
Python Basics
We won’t talk much about flow control, variables, operators, and all the
other things you need to know to use the language. For that, see the list of
additional resources at the end of the chapter.
The help function can be used on individual methods to provide more de-
tail. In exercises for the last chapter, we used the addVectorLayer method
to add a shapefile to the map. If we examine the help for this method we
get:
help(iface.addVectorLayer)
Help on built-in function addVectorLayer:
18 C H A P T E R 2 . P Y T H O N BA S I C S
addVectorLayer(...)
QgisInterface.addVectorLayer(QString, QString, QString)
-> QgsVectorLayer
See QString in the Python and C++ This not only shows us that the method requires three string arguments,
Types table in Section 5.2 on page 49.
but also tells us it returns a QgsVectorLayer object. While helpful, your
best resource is the QGIS API online documentation, which we cover in
Chapter 5, Navigating the QGIS API, on page 49.
• Lists
• Tuples
• Dictionaries
• Classes
Here you can see we created a list containing five items, four strings and
one integer. A list uses a zero-based index to refer to an item. As you
can see from the above example, you can use the colon notation to refer to
a range of items in the list. To get the last item in a list, use [-1].
You can mix object types in a list, including adding other lists:
>>> new_list = ["apple", my_list]
>>> new_list
['apple', ['GIS', 'QGIS', 'Python', 1, 'open source']]
>>> new_list[1]
['GIS', 'QGIS', 'Python', 1, 'open source']
>>> new_list[1][2]
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 2 . DATA S T RU C T U R E S 19
'Python'
The tuple
While we can change the value of an item in a list, you can’t do that with a
tuple:
>>> my_list
['GIS', 'QGIS', 'Python', 1, 'open source']
>>> my_list[1] = 'QGIS 2.0'
>>> my_list
['GIS', 'QGIS 2.0', 'Python', 1, 'open source']
>>> my_tuple
('GIS', 'QGIS', 'Python', 1, 'open source')
>>> my_tuple[1] = 'QGIS 2.0'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Tuples are faster than lists; use them when you need to iterate over a data
structure that never needs to change. Using Tuples - Examples where you
might want to use a tuple include days
of the week or months of the year.
These never change and you can it-
erate over them quicker with a tuple.
THIS BOOK IS C O P Y R I G H T E D — D O N OT D I S T R I B U T E
20 C H A P T E R 2 . P Y T H O N BA S I C S
The dict
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2.3. CLASSES 21
c++/python
The alternative (and often preferred) method is to wrap the code in a try/ex-
cept block:
>>> try:
... print my_dictionary['postgis']
... except:
... print "Key not found"
...
Key not found
2.3 Classes
Classes are an integral part of Python and you’ll be working with them ex-
tensively when programming PyQGIS. Python classes, just like their coun-
terparts in other object oriented languages, can have methods and attributes,
allowing you to model objects. In QGIS there are a large number of classes
representing map layers, data stores, legends, symbology, and much more. If you look back to the first chap-
ter, you’ll recognize that we already
worked with some QGIS classes
when exploring the Python console.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
22 C H A P T E R 2 . P Y T H O N BA S I C S
When creating a class, you need to think in terms of attributes that describe
it and what it can “do”. Here is a simple Python class that begins to model
a point:
The Point class has one attribute, marker_size and two methods: draw
and move. We can create and use the class as follows:
>>> from simplepoint import Point
>>> my_point = Point()
>>> my_point.marker_size
4
>>> my_point.draw()
drawing the point
>>> my_point.move()
moving the point
This trivial example of a class illustrates the basic concepts. Once you
instantiate your class, you can access the attributes and call methods.
We’ll use classes both in writing scripts that run from the console and in
plugins.
Subclassing a Class
Frequently you may need to create a class that inherits behavior from an
existing class. You do this by subclassing the class, which results in our
new class inheriting all the features of its parent.
Take for example the QgsPoint class. Using dir(QgsPoint) we can get a
list of its attributes:
>>> from qgis.core import QgsPoint
>>> dir(QgsPoint)
['__class__', '__delattr__', '__dict__', '__doc__',
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2.3. CLASSES 23
Let’s create a new class that adds a Z value to the QgsPoint class by sub-
classing it:
First we have to make sure we have imported the QgsPoint class from the
qgis.core module. Then in line 4 we define our new class Point3D, in-
heriting from QgsPoint. This will give our new class all the methods and
attributes of QgsPoint, plus those we will add.
In line 7 we call the base class __init__ method, passing it the values of x
and y. In line 8 we set z_value using the z argument that was passed.
If you look back at the attributes of QgsPoint, you’ll see there are methods
to set the X and Y values, as well as return them. We need the same methods
for our Z value. Lines 10 and 11 set the Z value and in lines 13 and 14 we
provide the code needed to return it. Here is an example of how we can use
our new class:
>>> from point3d import Point3D
>>> pt = Point3D(100, 100, 299)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
24 C H A P T E R 2 . P Y T H O N BA S I C S
>>> pt.z()
299
>>> pt.setZ(199)
>>> pt.z()
199
You can see our added methods work as expected. If we list the attributes
of Point3D, you’ll see all those of QgsPoint, as well as our new methods:
>>> dir(Point3D)
['__class__', '__delattr__', '__dict__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__gt__', '__hash__', '__init__', '__le__',
'__len__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'azimuth', 'multiply', 'onSegment', 'set',
'setX', 'setY', 'setZ', 'sqrDist', 'sqrDistToSegment',
'toDegreesMinutes', 'toDegreesMinutesSeconds', 'toString',
'wellKnownText', 'x', 'y', 'z']
There is a more work to do if we want to add full “Z” support to our new
point class, such as overriding the toString and wellKnownText methods to
include Z values. For example, if we call the toString method on our new
class, it returns the X and Y values, but our Z value is nowhere to be found:
>>> pt.toString()
u'100, 100'
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 4 . S T R I N G S , R A N G E S , A N D OT H E R H A N DY T H I N G S 25
The deeper you go into PyQGIS programming, the more you’ll find cases
where subclassing the QGIS API objects provides the functionality you
need.
There’s more to Python classes such as private variables and private meth-
ods. If you want to dig into the details, refer to some of the resources at the
end of this chapter.
Strings
You’ll use strings everywhere in your code. In this section we’ll use an
interactive Python session to illustrate some of the concepts.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
26 C H A P T E R 2 . P Y T H O N BA S I C S
Notice that in many of the string operations above, the result is a Python
list.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 4 . S T R I N G S , R A N G E S , A N D OT H E R H A N DY T H I N G S 27
Python comes with powerful string formatting capabilities. You can use
either the % operator or the format method:
>>> s = 'loves'
>>> "QGIS %s Python" % s
'QGIS loves Python'
>>> "QGIS {} Python".format(s)
'QGIS loves Python'
>>> # with two strings:
... s2 = 'Python'
>>> "QGIS %s %s" % (s, s2)
'QGIS loves Python'
>>> "QGIS {} {}".format(s, s2)
'QGIS loves Python'
Which should you use? It depends on whether you need the full power
of format or just need some simple formatting. The format method has a
lot of options for formatting strings and numbers, as well as using named
replacement fields:
>>> 'Location = {longitude}, {latitude}'.format(longitude=-150,
... latitude=60.5)
'Location = -150, 60.5'
Ranges
Ranges are handy when you need a list of integers to use in a for loop:
>>> range(0, 10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> range(1, 10, 2)
[1, 3, 5, 7, 9]
>>> range(100, 0, -10)
[100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
The third argument in the range function specifies the step—by default 1.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
28 C H A P T E R 2 . P Y T H O N BA S I C S
In a function with default arguments you specify a value for each optional
argument. Let’s say we have a function that draws a circle:
def draw_circle(radius, color='blue', line_width=1):
print radius, color, line_width
Using keyword arguments, we can call the same function like this:
>>> draw_circle(10, color='red')
10 red 1
>>> draw_circle(10, line_width=2)
10 blue 2
>>> draw_circle(10, line_width=2, color='red')
10 red 2
>>> draw_circle(line_width=2, color='red', radius=15)
15 red 2
The error seems misleading—the function takes at least one argument and
we gave it two, yet it still complained. This is because radius is mandatory
and we didn’t supply it.
Another useful way to call our function is using a dictionary with the **
operator:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 5 . I N S TA L L I N G PAC K AG E S 29
Here we passed the args dictionary to the function and it works similar
to using named arguments. Notice in the last example, we didn’t add the
line_width to our dictionary and the function used the default value we
specified in the function definition.
We’ve only touched on a small part of Python, but you’ll see some of the
things in the previous examples when we get into writing code. As always,
refer to the documentation at http://docs.python.org/2.
Once you have pip installed, it is a simple matter to search for and install
packages. Let’s take an example: say we want to install Sphinx, the docu-
mentation generator we will use to document our plugins. To install pip on Windows, see http:
//www.pip-installer.org
First let’s find the package name using pip:
ophir:pyqgis gsherman$ pip search Sphinx
Sphinx - Python documentation generator
sphinxcontrib-blockdiag - Sphinx "blockdiag" extension
sphinxcontrib-actdiag - Sphinx "actdiag" extension
dataflake.docbuilder - Automated Sphinx documentation builder
sphinxcontrib-nwdiag - Sphinx "nwdiag" and "rackdiag" extension
sphinxcontrib-seqdiag - Sphinx "seqdiag" extension
This confirms that the package exists and we have the correct name. In-
stalling it is simple:
ophir:pyqgis gsherman$ pip install sphinx
Downloading/unpacking sphinx
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
30 C H A P T E R 2 . P Y T H O N BA S I C S
Notice pip isn’t case sensitive---it was happy to install Sphinx even though
we used lower case in the install command. In addition to installing Sphinx,
7
pip freeze generates a list suit- pip also installed two dependencies for us: Pygments and docutils.
able for a requirements.txt file
which can be used by pip to install You can see which packages are installed on your system using pip list.
a set of packages using the -r or
--requirement switch.
Older versions of pip don’t support the list command—in this case you can
use pip freeze instead.7
Using pip list will generate an often lengthy list of all the packages in-
stalled on your system, along with the version number:
docutils (0.10)
docutils-ext (0.2d)
pdfrw (0.1)
pydns (2.3.6)
Pygments (1.6)
reportlab (2.7)
rst2pdf (0.93.dev)
see (1.0.1)
validate-email (1.1)
wsgiref (0.1.2)
Remember our example Point class? If we import it and look at the help we
get this:
>>> from point import Point
>>> help(Point)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2 . 6 . D O C U M E N T I N G YO U R C O D E 31
class Point
| Methods defined here:
|
| draw(self)
|
| move(self, new_x, new_y)
|
| ---------------------------------------
| Data and other attributes defined here:
|
| marker_size = 4
This is nice, but it is also sparse. We see the methods and attributes defined
but no description of what they actually do. Here is a new, documented
version of our code:
Now when we use the help function we can see our documentation for each
method
>>> from point import Point
>>> help(Point)
class Point
| Class to model a point in 2D space.
|
| Methods defined here:
|
| draw(self)
| Draw the point on the map canvas
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
32 C H A P T E R 2 . P Y T H O N BA S I C S
|
| move(self, new_x, new_y)
| Move the point to a new location on the
| map canvas
|
| ---------------------------------------
| Data and other attributes defined here:
|
| marker_size = 4
This gives us more information about what each method does. Many docu-
mentation generators (for example, Sphinx) use these docstrings to generate
a nicely formatted set of documentation for your code.
Keeping it Clean
As you write Python code, remember to keep it consistent and nicely for-
matted. One way is to use the pep8 tool to check your code. You can install
pep8 using pip or easy_install. To use it, run it from the command line
and pass the name of your Python script:
pep8 point.py
If you don’t get any results, it means your code passes, otherwise a list of
potential problems will be presented for you to consider and correct:
$ pep8 simplepoint.py
simplepoint.py:2:1: W293 blank line contains whitespace
Many of the errors may seem inconsequential, but it doesn’t hurt to keep
your code as clean as possible. For more information on formatting your
8
http://loc8.cc/ppg/pep8 Python code, see the Style Guide for Python Code.8
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
2.8. EXERCISES 33
In addition, there are a number of fine textbooks and tutorials available from
your online or local bookseller.
In reality, you can get started with PyQGIS with a modicum of Python
knowledge and pick up the rest along the way. Feel free to dig in and get
started.
2.8 Exercises
1. Write a function to accept an x and y value and using string format-
ting, print it with a precision of four decimal places.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
3
Setting Up Your Development Tools
1. Python 2.x
3. PyQt
3.1 Python
Depending on your operating system, you may find that you already have
Python installed.
Python is usually installed as part of Linux and OS X. You can easily test
this by opening a terminal or shell prompt and typing:
python
$ python
Python 2.7.2+ (default, Oct 4 2011, 20:06:09)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
If your version number is 2.x then you are set to go. If not, or attempt-
ing to run python gives an error, you’ll have to install a proper version of
Python. On Linux, use your package manager to locate and install Python.
Mac OS X versions through Mavericks come with Python 2.x.
Windows
Unless you build your own version of QGIS from source, you’ll use either
the Standalone or OSGeo4W installer.9 This will provide you with a version
9
http://loc8.cc/ppg/installer of Python and associated libraries/modules that work with QGIS.
When installing, do yourself a favor and choose a path that doesn’t contain
spaces to house your new QGIS installation. This will make setting up for
your PyQGIS work much simpler.
If you are using Windows, installing QGIS using the OSGeo4W Installer
will give you everything you need to develop with PyQGIS.
If you choose to use an editor, make sure you choose one that provides
syntax highlighting and indentation for your code. Here are some of the
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
3 . 2 . I D E O R E D I TO R 37
• Vim - http://www.vim.org
• Emacs - http://www.gnu.org/software/emacs
• Notepad++ - http://notepad-plus-plus.org
• jEdit - http://www.jedit.org
There are a bunch of editors out there that will do the job. Choose one you
are comfortable with and makes coding easier. For a comprehensive list,
see the Python Wiki.10 10
http://loc8.cc/ppg/editors
Using an IDE
While you can write code in an editor and debug it from a shell, an IDE
can make the process easier, especially if you want to debug your plugin in
QGIS. There are a lot of Python IDE’s to choose from—some of them are
free and others are not. Let’s take a look at a couple of them.
PyDev
PyDev is a freely available IDE for developing with Python. Below you
will find instructions for installing the Eclipse framework and PyDev.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
38 C H A P T E R 3 . S E T T I N G U P YO U R D E V E L O P M E N T TO O L S
7. Click OK
8. Once the list is populated, click the checkbox next to PyDev and click
the Next button
10. Once the install is complete, you will be prompted to restart Eclipse
11. Once Eclipse restarts, open the PyDev perspective by choosing Window->Open
Perspective->Other from the menu
PyCharm
PyCharm provides code completion for both PyQt and the QGIS modules,
making it easier to get started writing code. It also has a Vim emulator for
those of us that cherish that means of editing code.
As I said, there are a lot of other Python IDE’s out there; some free and
some not. For a fairly comprehensive collection, see the list on the Python
11
http://loc8.cc/ppg/ide Wiki.11
3.3 Qt/PyQt
PyQt is the Python API interface to Qt, the C++ framework that QGIS is
based on. Because QGIS is built on the Qt framework, we use PyQt when
creating plugins to provide all the GUI elements we might need.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
3.3. QT/PYQT 39
Installing PyQt
Depending on your platform, you may already have Qt and PyQt installed.
If you are using the Standalone or OSGeo4W Windows installers, you al-
ready have Qt and PyQt installed as part of the QGIS installation.
For Mac users, the Kyngchaos binaries include both the Qt and PyQt run-
time libraries. If you want to create user interfaces using Qt Designer, you’ll
have to install the Qt Tools package that is part of the full Qt install for
Mac.12 12
http://download.qt-project.
org
On Linux, you can use your package manager to install Qt and PyQt. If you
want to develop user interfaces, be sure to get both the Qt and PyQt tools
packages.
Next up, we’ll look at the QGIS/Python “ecosystem” and how plugins work
with QGIS.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4
The QGIS/Python Ecosystem
This chapter explains a bit of history and how Python and QGIS work to-
gether. You’ll also learn how to manage Python plugins in QGIS.
4.1 History
Early in the development of QGIS, a plugin architecture was designed to
provide a way to dynamically add new features and capabilities. This archi-
tecture was created using C++. Since writing a QGIS plugin in C++ is not
a trivial task, the number of potential contributors was limited.
With the first release of Python support, the number of contributed plugins
grew at an exceptional rate, contributing to the overall growth of QGIS as a
first-class desktop GIS.
“sip” files are compiled as part of QGIS to provide the Python interface to
13
http://loc8.cc/ppg/py_sip the classes. The Python support is divided into several modules:
To use the QGIS classes in one of the modules, you simply import it in
Python, then call the desired methods and/or access the attributes:
>>> from qgis.core import *
>>> v = QgsVectorLayer()
>>> v.setTitle('Sample Layer')
>>> v.title()
u'Sample Layer'
In this book, we’ll focus on the use of the first two modules. As a PyQGIS
developer, you generally don’t need to be concerned about how the integra-
tion works; everything you need is built-in to your QGIS distribution.
Contributed plugins can become core plugins if they are found to provide
key functionality advantageous to the majority of QGIS users.
You can find and install contributed plugins from one or more plugin repos-
itories.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4 . 4 . P L U G I N R E P O S I TO R I E S 43
Anyone can contribute a Python plugin to the official repository. Later we’ll
see how to build and package your plugin so it is ready to be shared with
other QGIS users.
Figure 4.1, on the next page, shows what the Plugin Manager looks like
when first opened from the menu. The default view is a list of installed
plugins, along with some management functions to enable/disable, upgrade,
uninstall, and reinstall a plugin. Plugins that have a check mark are those
that are currently enabled. Additionally, the right-panel gives you some
information about the installed plugins and how to enable/disable them.
You can distinguish between core and contributed plugins by examining the
directory where each is installed. Core plugins are installed in the same di-
rectory as the QGIS application. Contributed (Python) plugins are installed
in a subdirectory of your HOME directory (see Section 4.6, Python Plugin
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
44 CHAPTER 4. THE QGIS/PYTHON ECOSYSTEM
Plugins are installed using the Plugin Manager. When you first open the
Plugin Manager, the configured repositories are queried and the list of plu-
gins is updated and displayed by category: Installed, Get more, Upgrade-
able, and Invalid.
To install a new plugin, select Get more from the left panel and locate the
plugin of interest in the list, highlight it, and click the Install plugin button
to complete the install
Figure 4.2, on the next page shows the Plugin Manager ready to install the
PinPoint plugin.
Use the Search box to narrow the list of plugins based on name, descrip-
tion, tags, or author.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4 . 5 . M A NAG I N G P L U G I N S 45
Clicking Settings in the left panel of the Plugin Manager brings up the
option panel shown in Figure 4.3, on the following page.
You can control how often QGIS checks for plugin updates by selecting an
interval from the drop-down box.
Some plugins are tagged as experimental by their authors. This means they
may not be ready for prime time, have an incomplete feature set, or may
have bugs. You can choose to have these shown in the Plugin Manager by
clicking the checkbox.
On the lower half of the settings panel you’ll find the section that allows
you to add repositories. Out of the box, QGIS has the master repository
configured for you. If you need access to other repositories (such as one
you set up for development purposes), add them to the list by giving them
a name and the URL that points to the XML file describing the repository.
We’ll show you how to set up your own repository in Section 8.14, Setting
Up a Repository, on page 115.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
46 CHAPTER 4. THE QGIS/PYTHON ECOSYSTEM
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
4.7. EXERCISES 47
the Plugin Manager to manage your plugins. We’ll take a look at develop-
ment practices and how to package a plugin in Chapter 10, Writing Plugins,
on page 131.
Now let’s move on and take a look at navigating the QGIS API.
4.7 Exercises
1. Run QGIS and use the Plugin Manager to install the following plug-
ins (we’ll need them later on):
a. ScriptRunner
b. Plugin Builder
4. Use the Plugin Manager to disable ScriptRunner and note the changes
to the menu and plugin toolbar.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5
Navigating the QGIS API
Before we can really get into PyQGIS programming, we need a basic un-
derstanding of how to use both the QGIS and Qt API documentation. Since
there are over 500 QGIS classes listed in the documentation, we obviously
won’t be going through them all. In this chapter we’ll try to give you a
broad overview, as well as specifics on how to decipher the documentation.
The documentation for the constructor (the way we create the object) for
QgsVectorLayer looks like this:
QgsVectorLayer (QString path=QString::null,
QString baseName=QString::null,
QString providerLib=QString::null,
The QGIS API documentation is C++ bool loadDefaultStyleFlag=true)
centric, meaning all the types you’ll
see are C++ types. Don’t worry, there First you’ll notice the types specified are QString and bool. QString is a Qt
are direct equivalents in Python that
class and bool is a C++ boolean type. Since C++ is statically typed, each
we’ll point out.
argument, variable, and return value must have a type specified.
Since PyQt “maps” Python types to C++ types, we can just use Python
strings to create the layer.
Python and C++ Types
Here are some Python equivalents to the Qt/C++ types you’ll see in the API
documentation:
Qt/C++ Python
QList, QSet, QVector list
QMap dict
QString string
bool bool
Notice we didn’t specify a value of loadDefaultStyleFlag. In the documen-
tation, any argument followed by an equal sign is optional. By not spec-
ifying loadDefaultStyleFlag, it defaulted to True. In fact, we can create a
QgsVectorLayer without specifying any arguments:
layer = QgsVectorLayer()
This is because all of the arguments in the constructor are optional. Al-
though we can create a layer in this manner, it isn’t very useful:
>>> layer = QgsVectorLayer()
>>> layer.isValid()
False
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 3 . S I G NA L S A N D S L OT S 51
Although layer is a valid object, it isn’t a valid layer since no data source or
provider was specified in its creation. Just because a constructor or method
has default values doesn’t mean we should always depend on them.
Let’s take a look at another QGIS class: QgsPoint, which represents a sim-
ple 2D point. In the QGIS documentation, we find three ways to construct
a point:
QgsPoint ()
QgsPoint (const QgsPoint &p)
QgsPoint (double x, double y)
The first method creates an “empty” point object at 0,0. The second creates
a new point from an existing one, and the last creates a point from supplied
x and y values:
>>> p1 = QgsPoint()
>>> p1
(0,0)
>>> p2 = QgsPoint(21.2, 100.9)
>>> p2
(21.2,100.9)
>>> p3 = QgsPoint(p2)
>>> p3
(21.2,100.9)
Now that we have a basic idea of how individual classes are documented,
let’s take a brief side trip into the world of signals and slots.
Menu and toolbar items in Qt (and therefore QGIS) are usually created
using a QAction object:
self.zoomin_action = QAction(
QIcon(":/ourapp/zoomin_icon"),
"Zoom In",
self)
This creates an action with an icon and a text label of “Zoom In”. An action
must be added to a menu and/or toolbar before it is of any practical use.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
52 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
• changed()
• hovered()
• toggled(bool)
14
http://loc8.cc/ppg/pyqt_ • triggered(bool = 0)
qaction
Although the other signals may be of interest, in this example we will focus
on the triggered signal. This signal is emitted when the user clicks on the
menu item or toolbar button or presses the action’s keyboard shortcut.
This means that when our zoomin_action is triggered, the zoom_in method
(defined elsewhere in our code) will be called.
You can think of it as a listener that waits for a signal to be emitted and then
does something in response. For an example of an action connected to a
slot, see Section 12.4, Adding Map Tools to the Application, on page 168.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 4 . T H E L AYO U T O F Q G I S A N D P Y Q T D O C U M E N TAT I O N 53
Public Slots
These are methods we can connect to from a QAction or some other Qt GUI
element such as a drop-down box or checkbox.
While these are slots, they can be called like any other method in the class:
>>> lyr = QgsVectorLayer('/data/alaska.shp', 'Alaska', 'ogr')
>>> QgsMapLayerRegistry.instance().addMapLayer(lyr)
<qgis.core.QgsVectorLayer object at 0xc3b90bc>
>>> lyr.metadata()
u'<html><body><p class="subheaderglossy">General</p>\n<p
class="glossy">Storage type of this layer</p>\n<p>ESRI
Shapefile</p>\n<p class="glossy">Description of this
provider</p>\n<p>OGR data provider (compiled against GDAL/OGR
library version 1.10.0, running against GDAL/OGR library
version 1.10.0)</p>\n<p class="glossy">Source for this
layer</p>\n<p>/data/alaska.shp</p>\n<p
class="glossy">Geometry type of the features in this
layer</p>\n<p>Polygon</p>\n<p class="glossy">The number of
features in this layer</p>\n<p>653</p>\n<p
class="glossy">Editing capabilities of this layer</p>\n<p>Add
Features, Delete Features, Change Attribute Values, Add
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
54 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
The metadata() method returns the metadata for our layer in HTML format.
QTextBrowser: If you were developing a plugin or standalone application and want to dis-
http://loc8.cc/ppg/pyqt_qtb
play the metadata for a layer in a QTextBrowser you could connect a button
click to the slot to fetch the HTML, then insert it into your QTextBrowser.
Signals
QgsVectorLayer has a fair number of signals; here a few of them from the
documentation:
void attributeAdded (int idx)
Will be emitted, when a new attribute has been added to this
vector layer.
void attributeDeleted (int idx)
Will be emitted, when an attribute has been deleted from this
vector layer.
void attributeValueChanged (QgsFeatureId fid, int idx, const QVariant &)
void beforeCommitChanges ()
Is emitted, before changes are commited to the data provider.
void beforeRollBack ()
Is emitted, before changes are rolled back.
...
void editingStarted ()
Is emitted, when editing on this layer has started.
void editingStopped ()
Is emitted, when edited changes successfully have been written
to the data provider.
Any time you want to perform an action based on some event in your plugin
or application, you would connect one of these signals to the appropriate
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 4 . T H E L AYO U T O F Q G I S A N D P Y Q T D O C U M E N TAT I O N 55
We won’t list the methods here—just remember that when you want to
know what a QGIS class can do, look at the public member functions.
Static functions or methods are different than the methods we’ve mentioned
previously. Most of the methods require an instance of the class before you
can access them. In this snippet of code, layer is the variable that holds an
instance of QgsVectorLayer:
layer = QgsVectorLayer('/data/alaska.shp', 'Alaska', 'ogr')
Since the static method in QgsVectorLayer isn’t very suitable for our ex- You can access the Log Messages
panel by clicking on the yellow trian-
ample, let’s illustrate using QgsMessageLog, a class that writes an entry to
gle in the lower right of the status bar.
the Log Messages panel in QGIS. Here is a list of static methods available
in QgsMessageLog:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
56 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 5 . C H O O S I N G B E T W E E N T H E Q T A N D P Y Q T D O C U M E N TAT I O N 57
This static method gives us a little warning message as shown in Figure 5.2.
Static public methods are quick and easy to use and you’ll find them through-
out both the QGIS and Qt API. Be sure to look through the API documen-
tation for a class to familiarize yourself with the methods available.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
58 C H A P T E R 5 . NAV I G AT I N G T H E Q G I S A P I
different between the two. This is because of the differences between C++
and Python. In C++ many of the methods “fill” a variable you provide to
the method. In these situations, Python returns a tuple or a list.
Let’s look at a simple example using the static method getText in QInputDialog.
The getText method allows you to present a simple dialog box to the user
and get a string in return.
If we provide the ok argument, getText will fill it with true if the user
pushes the OK button and false if they cancel the dialog. In C++ we
would do something like:
bool ok;
QString text = QInputDialog::getText(this, "Enter Your Name"),
"Name:", QLineEdit::Normal,
'Nobody', &ok);
When the dialog is closed, we could check to see if ok is true and then
proceed to use the string returned in our text variable.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
5 . 5 . C H O O S I N G B E T W E E N T H E Q T A N D P Y Q T D O C U M E N TAT I O N 59
While this works, assigning the result to variables (name, ok) makes for
code that is easier to read and understand.
Next we’ll fire up the QGIS Python console and start using some of our
PyQGIS skills.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6
Using the Console
The QGIS Python console is great for doing one-off tasks or experimenting
with the API. Sometimes you might want to automate a task using a small
script, and do it without writing a full blown plugin.
In this chapter we’ll take a look at using the console to explore the workings
of PyQGIS, as well as doing some real work.
The lower panel of the console is the input area; results of input are dis-
played in the upper panel.
On the left of the console you’ll see a toolbar that contains the following
tools, top to bottom:
Clear console: Clears the console of all previous commands and output
Console Options
Clicking the Settings button brings up the console options dialog as shown
in Figure 6.2, on the next page.
You can set the font for both the Console and the Editor, as well as enabling
autocompletion. For the Editor, you can enable the object inspector which
allows you to get a nice view of your code.
Lastly, you can choose to use the preloaded API files or untick the box and
manually add your own. Generally you can stick with the preloaded files
as they contain the information needed for autocompletion in the PyQt and
QGIS API.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 2 . U S I N G T H E C O N S O L E E D I TO R 63
writing concise scripts, prototyping, and testing. Usually you will want to
use your favorite editor or IDE when working on a larger project.
The Toolbar
The Console Editor toolbar is arranged vertically, with the following tools
top to bottom:
Open file: Open a Python script located on disk. Multiple files can be
opened, each assigned to a new tab.
Save As...: Save the current script to a file with a new name or location
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
64 CHAPTER 6. USING THE CONSOLE
Find Text: Search the current script for occurrences of a given text
string
Paste: Paste the contents of the clipboard at the current cursor location
The simple_point.py script can Let’s load the simple_point.py script that we looked at in Chapter 2,
be found in http://locatepress.
Python Basics, on page 17 and take a look at using it in the Console Editor.
com/files/pyqgis_code.zip,
which includes all the code samples
in the book.
Figure 6.3, on the next page shows our script loaded into the editor and the
Object Inspector panel visible. Here’s what we did to get to the point shown
in the figure:
4. Using the Editor, add the my_function function to the bottom of simple_point.py
6. Open the Object Inspector and click the ’+’ to expand the Point node
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 2 . U S I N G T H E C O N S O L E E D I TO R 65
When we click Run script in the Editor, it executes our code in the console.
Since our code just defines a class and one function, there is no output in
the console, however our class and function are now available for use.
In the console panel (left side), you can see the output from our commands.
We’ll take another look at this when we get to Section 7.1, Standalone
Scripts in the Console, on page 73.
Now that we have an overview of the console and editor, let’s put it to use.
In the Introduction, we used the console to manipulate the map view in
QGIS using methods exposed by the qgis.util.iface object. We’ll take it a
bit further now to actually load some data and work with the interface.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
66 CHAPTER 6. USING THE CONSOLE
With QGIS running, open the console using the Plugins->Python Console
menu. Since the qgis.core and qgis.gui modules are imported at startup,
we can start using the API immediately.
path:
The full path to the layer
basename:
A name to be used in the legend
providerLib:
The data provider to be used with this layer
loadDefaultStyleFlag:
Use the default style when rendering the layer. A style file has a .qml
extension with same name as the layer and is stored in the same location.
It is possible to create a vector layer that isn’t valid. For example, we can
specify a bogus path to a shapefile:
>>> bogus = QgsVectorLayer('/does/not/exist.shp', 'bogus_layer', 'ogr')
>>> bogus
<qgis.core.QgsVectorLayer object at 0x1142059e0>
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 3 . L OA D I N G A V E C TO R L AY E R 67
Notice there is no complaint from QGIS, even though the shapefile doesn’t
exist. For this reason, you should always check the validity of a layer before
adding it to the map canvas:
>>> bogus.isValid()
False
If the isValid() method returns False, there is something amiss with the layer
and it can’t be added to the map.
Getting back to our valid, world_borders layer, you’ll notice nothing hap-
pened on the map canvas. We created a layer, however, for it to draw, we
need to add to the list of map layers. To do this, we call a method in the
QgsMapLayerRegistry:
QgsMapLayerRegistry.instance().addMapLayer(wb)
Once we do that, the layer is drawn on the map as shown in Figure 6.4.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
68 CHAPTER 6. USING THE CONSOLE
The layer is removed from both the map canvas and the legend, then the
map is redrawn.
To get a bit of information about the symbol, we can use the dump() method:
>>> symbol.dump()
u'FILL SYMBOL (1 layers) color 134,103,53,255'
The output shows us that our layer is rendered using a fill symbol (which
we already knew) using a color with RGB values of 134, 103, 53 and no
transparency. Let’s change the color to a dark red and refresh the map:
>>> from PyQt4.QtGui import QColor
>>> symbol.setColor(QColor('#800000'))
Let’s analyze what we did here. To be able to change the color, we imported
the QColor class from the PyQt4.QtGui module. The setColor method takes
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 4 . E X P L O R I N G V E C TO R S Y M B O L O G Y 69
• QColor(Qt.red)
• QColor(’red’)
• QColor(’#ff0000’)
• QColor(255,0,0,255)
If you try the first method, you’ll get an error because we haven’t imported
Qt. The fix is to import it prior to referencing Qt.red:
from PyQt4.QtCore import Qt
The last method is interesting in that it includes a value for the alpha-
channel (transparency). Using this method of creating the color, we can
also set the transparency of the layer. Each of these methods is described in
the QColor documentation.
You’ll notice that nothing happens on the map canvas when we change the
color. We must tell QGIS to update the map canvas to reflect the changes to
our layer:
>>> iface.mapCanvas().refresh()
This should redraw the map and our layer is now filled with a dark red color.
The other is to test to see if there is a cached image for the layer and, if so,
invalidate it and then refresh:
>>> if wb.cacheImage() != None:
... wb.setCacheImage(None)
... iface.mapCanvas().refresh()
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
70 CHAPTER 6. USING THE CONSOLE
In practice, the first method should work in all situations, even if it is a bit
“tacky” if render caching is not enabled.
Now that the layer is rendered in our new color, take a look at the legend—it
still shows the previous color. To update it we need to call the refreshLay-
erSymbology method of the legend interface:
iface.legendInterface().refreshLayerSymbology(wb)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
6 . 6 . DATA P ROV I D E R S 71
At present there are over ten data providers included with QGIS, covering
a large portion of the data stores you will encounter, from file-based to
relational databases.
You may be wondering what data providers are available and how you
specify them when loading layers. The QgsProviderRegistry class is re-
sponsible for registering all the data providers when QGIS starts up—the
providerList() method will return a list of all providers. From the console
you can display all the available provider keys:
>>> for provider in QgsProviderRegistry.instance().providerList():
... print provider
WFS
delimitedtext
gdal
gpx
memory
mssql
ogr
osm
postgres
spatialite
sqlanywhere
wms
In QGIS, you can use the Providers tab in the About dialog to view a de-
scription of all the available providers. The list of provider keys above
corresponds to:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
72 CHAPTER 6. USING THE CONSOLE
• Memory provider
• MSSQL spatial data provider
• OGR data provider (compiled against GDAL/OGR library version 1.9.1,
running against GDAL/OGR library version 1.9.1)
• Open Street Map data provider
• PostgreSQL/PostGIS data provider
• SpatiaLite data provider
• SQL Anywhere data provider
• OGC Web Map Service version 1.3 data provider
Now that we have some experience with the console, let’s move on to run-
ning scripts using a couple of different methods.
6.7 Exercises
1. Using the console, add the world borders layer to the map canvas,
then:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7
Running Scripts
PyQGIS scripts are a good way to perform tasks within QGIS without re-
sorting to writing a plugin. It’s easy to integrate scripts into your workflow
and use them to load and style layers, manipulate the map view, and control
a lot of QGIS functionality.
In this chapter we’ll take a look at running standalone scripts and then use
both the Python Console and the Script Runner plugin to load, manage, and
run our scripts.
Our task list may look a bit ambitious, however it won’t take much code to
accomplish:
Let’s take a look at how this script works. In lines 1 through 4 we import the
modules we’ll need. When writing anything other than a trivial script, you’ll
find you usually need to import PyQt4.QtCore, PyQt4.QtGui, qgis.core, and
Depending on how we run our script, qgis.gui. In this case we didn’t need the qgis.gui module. We also imported
we may not need the qgis.utils.iface
qgis.utils.iface since it provides access to some of the functions we’ll need.
import. If executed from the Python
console editor, the import is not
needed. To make the script flexible,
The script is divided into three methods that do the work:
it’s best to include it.
• load_layer
• change_color
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 1 . S TA N DA L O N E S C R I P T S I N T H E C O N S O L E 75
• open_attribute_table
All of the methods used in the script were explained in the previous chap-
ters, so let’s look at how to actually run this script in the QGIS Python
console.
The first thing we need to do is make sure QGIS (and Python) can find our
script. If you load and run your script from the editor you don’t have to
worry about setting the path, however, you may want to run scripts from the
console prompt. In this case, there are a number of ways to set the path:
• Linux: /home/gsherman/pyqgis_scripts
• Mac: /Users/gsherman/pyqgis_scripts
• Windows: C:/pyqgis_scripts
Let’s take a look at each of these methods and see how we might use them.
If we want to run this on Windows, the only difference is the append state-
ment:
>>> sys.path.append('C:/pyqgis_scripts')
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
76 C H A P T E R 7 . RU N N I N G S C R I P T S
If you get errors when importing your script, check to make sure you have
the path to pyqgis_scripts specified correctly in either sys.path, the PYTHONPATH
environment variable, or the QGIS options setting.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 1 . S TA N DA L O N E S C R I P T S I N T H E C O N S O L E 77
When you call each of the methods in our script, you’ll see that the world_borders
layer is added to the map, the color is changed to red and the legend is up-
dated. The last method opens the attribute table for our layer.
We can make our script a bit more elegant by refactoring it into a Python
class. This allows us to persist data during the lifetime of the class and
makes the code more friendly. The method(s) of setting the Python path
remain the same, but the way in which we use the class to execute the code
is a bit different. Here is the original script, refactored into a class:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
78 C H A P T E R 7 . RU N N I N G S C R I P T S
4
5
6 class FirstScript:
7
8 def __init__(self, iface):
9 self.iface = iface
10
11 def load_layer(self):
12 wb = QgsVectorLayer('/data/world_borders.shp', 'world_borders', 'ogr')
13 QgsMapLayerRegistry.instance().addMapLayer(wb)
14
15 def change_color(self):
16 active_layer = self.iface.activeLayer()
17 renderer = active_layer.rendererV2()
18 symbol = renderer.symbol()
19 symbol.setColor(QColor(Qt.red))
20 self.iface.mapCanvas().refresh()
21 self.iface.legendInterface().refreshLayerSymbology(active_layer)
22
23 def open_attribute_table(self):
24 self.iface.showAttributeTable(self.iface.activeLayer())
Let’s take a look at the class to see how it’s different from our original
script. First, notice that we have added a new method named __init__. This
method is called when you create an instance of a class. Since a reference to
qgis.utils.iface is passed and stored (self.iface in line 9), we no longer need
to import iface from qgis.utils. Now when a new instance of FirstScript is
created, we have access to the iface attribute:
>>> fs = FirstScript(qgis.utils.iface)
>>> fs.iface
<qgis.gui.QgisInterface object at 0x1130d0440>
For an explanation of the use of self, The remaining methods in our class look similar to those in the original
see http://loc8.cc/ppg/self
script, except each is defined with a self argument as seen in lines 11, 15,
and 23 of first_script_class.py. This is the standard way of defin-
ing class methods. The other difference is, we use self.iface to access the
activeLayer, refreshLayerSymbology, and showAttributeTable methods.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 2 . RU N N I N G S C R I P T S W I T H S C R I P T RU N N E R 79
This provides us with a bit cleaner and more object oriented approach to
our script.
Figure 7.2 shows the Script Runner plugin after you first start it up.
To run an existing script, you first add it to Script Runner using the Add
Existing Script tool on the toolbar. This will add it to a list in the left panel
and Script Runner will remember the next time you start it up. You can
remove a script using the Remove Script tool. This just removes it from the
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
80 C H A P T E R 7 . RU N N I N G S C R I P T S
Once you have a script loaded, you can click the Script Info tool to populate
the Info and Source tabs in the panel on the right. The Info tab contains the
docstring from your module and a list of the classes, methods, and functions
found in the script. Having a proper docstring at the head of each script will
help you easily determine its purpose. You can view the source of the script
on the Source tab. This allows you to quickly confirm that you are using the
right script.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 2 . RU N N I N G S C R I P T S W I T H S C R I P T RU N N E R 81
31 fs.open_attribute_table()
Note that the run_script method is not indented the same as the other meth-
ods and is not part of the FirstScript class.
We can now add our script to Script Runner by clicking the Add Script
button and choosing it from our scripts directory. Once it is added, click
on the Info tab to get some basic information about the script as shown in
Figure 7.3.
Notice that it tells us we have no Docstring and we “really should add one.”
Let’s learn how to fix that now.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
82 C H A P T E R 7 . RU N N I N G S C R I P T S
class FirstScript:
...
If we reload the script in Script Runner, the docstring is displayed in the Info
tab. You can also document your class and methods, however Script Runner
doesn’t display them. Once documented, we can use help in the Python
interpreter or the QGIS Python console to display the documentation. You
can also use help to display the docstring for individual methods:
>>> from first_script_sr import FirstScript
>>> fs = FirstScript(qgis.utils.iface)
>>> help(fs.load_layer)
Help on method load_layer in module first_script_sr:
>>> help(fs.change_color)
Help on method change_color in module first_script_sr:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
7 . 2 . RU N N I N G S C R I P T S W I T H S C R I P T RU N N E R 83
3
4 from PyQt4.QtGui import *
5 from PyQt4.QtCore import *
6 from qgis.core import *
7
8
9 class FirstScript:
10 """Class to load and render the world_borders shapefile."""
11
12 def __init__(self, iface):
13 self.iface = iface
14
15 def load_layer(self):
16 """Load the world_borders shapefile and add it to the map."""
17 wb = QgsVectorLayer('/data/world_borders.shp', 'world_borders', 'ogr')
18 QgsMapLayerRegistry.instance().addMapLayer(wb)
19
20 def change_color(self):
21 """Change the color of the active layer to red and update
22 the legend."""
23 active_layer = self.iface.activeLayer()
24 renderer = active_layer.rendererV2()
25 symbol = renderer.symbol()
26 symbol.setColor(QColor(Qt.red))
27 self.iface.mapCanvas().refresh()
28 self.iface.legendInterface().refreshLayerSymbology(active_layer)
29
30 def open_attribute_table(self):
31 """Open the attribute table for the active layer."""
32 self.iface.showAttributeTable(self.iface.activeLayer())
33
34
35 def run_script(iface):
36 """Run the script by instantiating FirstScript and calling
37 methods."""
38 fs = FirstScript(iface)
39 fs.load_layer()
40 fs.change_color()
41 fs.open_attribute_table()
You may have realized that using Script Runner has several advantages. For
one, we don’t have to fiddle with our Python path to run our script. Script
Runner takes care of all that once you add a script. Additionally, having all
your scripts organized in a neat list is handy, regardless of where they are
actually located on disk. The information and source viewing features of
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
84 C H A P T E R 7 . RU N N I N G S C R I P T S
Best of all, enabling an existing script to work with Script Runner is a sim-
ple matter of adding the run_script method, creating an instance of your
class, then calling one or more methods to do the job.
In the next chapter we’ll take a look at a number of tips and techniques you
will find useful when working with PyQGIS.
7.3 Exercises
1. The first_script.py script doesn’t check to see if the data layer is
valid. Modify first_script.py to implement this check and handle
any failures gracefully. Test your changes in the console.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8
Tips and Techniques
OGR Layers
This creates a layer from a shapefile and adds it to the map. Here are details:
imports:
qgis.core
86 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
classes:
QgsMapLayerRegistry, QgsVectorLayer
provider:
OGR
This example will work with any OGR supported layer. For example, we
can load a GML layer using:
gml_lyr = QgsVectorLayer('/data/qgis_sample_data/gml/lakes.gml',
'lakes',
'ogr')
QgsMapLayerRegistry.instance().addMapLayer(gml_lyr)
Memory Layers
QGIS also supports what’s known as a memory layer which can be very use-
ful when writing plugins and scripts. A memory layer doesn’t exist on disk
and disappears when QGIS exits, but behaves like any other vector layer.
If needed, a memory layer can be saved to disk in any format supported by
QGIS.
The second method is much quicker and easier to use because we can spec-
You can create memory layers con- ify the coordinate system (CRS) and the fields all at once:
taining other geometry types, includ-
ing Point, MultiLineString, and mem_layer = QgsVectorLayer(
Polygon. "LineString?crs=epsg:4326&field=id:integer"
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 . L OA D I N G V E C TO R L AY E R S 87
"&field=road_name:string&index=yes",
"Roads",
"memory")
This creates a linestring memory layer with two fields (id and road_name)
using the WGS84 coordinate system. We also included the ’index=yes’
keyword which tells the provider to create a spatial index for the features.
If we open the layer properties dialog and look at the Fields, we can see the
results as shown in Figure 8.1.
Looking at the Metadata also confirms that our layer was created success-
fully:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
N
88 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Our new memory layer fully supports editing of both features, attributes,
and geometries. Since we just created it, there are no features and QGIS
reports the extent as unknown.
There are a couple of important points to note in the listing. First, in order
to add a feature to the layer we need to call the startEditing() method.
In line 3 we create a new QgsFeature object and in line 4 set the geometry
by passing the points list to the QgsGeometry.fromPolyline method.
Line 5 adds the attributes for our new feature: id of 1 and the road_name of
’QGIS Lane’.
In line 6 we add the feature to the memory layer and then commit the
changes in line 7. Now if we zoom to the layer extent we will see a sin-
gle road. Opening the attribute table confirms that the feature was created
and the attributes properly added.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 2 . L OA D I N G R A S T E R L AY E R S 89
Using iface to Load Layers
From a plugin or the console, you can add both vector and raster lay-
ers using iface methods (addVectorLayer and addRasterLayer). For
example:
Using this method saves you a step; the layer is added to the map without
having to call QgsMapLayerRegistry.instance().addMapLayer(). In our
examples, we used the two step method to help you understand the process.
When developing a standalone application using the QGIS API, you
won’t have access to the iface object.
The easiest way to figure out how to add a PostGIS layer is to add it using
the QGIS interface, then look at the connection information in the Metadata
tab of the layer properties dialog. This will give you all the parameters
needed to make a successful connection and add a layer.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
90 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
When we added disk-based layers (e.g. shapefile, GML), we used the full
path to the layer—the three lines above do the same for our database layer.
The remaining arguments are streets3, the name that will appear in the
legend, and postgres, the provider key.
Sometimes you may want to send SQL to your PostgreSQL database from
a plugin or PyQGIS script. To do this, you need a connection to your
database. Since QGIS uses its own low-level interface to PostgreSQL, it
can’t provide a connection for general query use. You can, however, use the
information from the QGIS PostgreSQL data provider to create your own
connection using QSqlDatabase, a Qt class.
You could use QSettings to read the stored connection information from
your settings, but this may not always work if the password isn’t stored
with the connection.
Here is an example that uses the active layer in QGIS to get the connection
parameters, create a connection, and execute a query that returns some data:
1 """Use the database provider to connect to PostgreSQL.
2 This script is set up to run under the Script Runner plugin, but
3 can be also be run from the Python console.
4 """
5
6 from PyQt4.QtCore import *
7 from PyQt4.QtGui import *
8 from PyQt4.QtSql import *
9
10 from qgis.core import *
11
12
13 def run_script(iface):
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 91
Simple Symbology
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
92 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
reference to a layer (layer), we can change the look in the following ways:
renderer = layer.rendererV2()
symbol = renderer.symbol()
symbol.setColor(QColor(Qt.Red))
symbol.setColor(QColor('red'))
symbol.setColor(QColor('#ff0000'))
symbol.setColor(QColor(255, 0, 0, 255))
Each of the setColor statements above will set the symbol color to red.
After setting the color we must refresh the map canvas and the legend:
iface.mapCanvas().refresh()
iface.legendInterface().refreshLayerSymbology(layer)
Setting Transparency
This sets the transparency for the symbol to 50%. Be sure to refresh the
canvas and legend after making changes.
Customizing Symbols
For each symbol type (marker, line, fill) we can set additional properties to
control the appearance using the createSimple method. This method is
available for:
• QgsMarkerSymbolV2
• QgsLineSymbolV2
• QgsFillSymbolV2
angle
Specified in radians
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 93
color
Specified using any of the QColor constructor methods
color_border
Specified using any of the QColor constructor methods
horizontal_anchor_point
Integer value
name
Name of the marker (e.g. circle, square, etc)
offset
Specified as an x,y integer offset
offset_unit
MM or MapUnit
outline_width
Integer value
outline_width_unit
MM or MapUnit
scale_method
diameter or area
size
Integer value
size_unit
MM or MapUnit
vertical_anchor_point
Integer value
capstyle
’square’, ’flat’, or ’round’
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
94 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
color
Specified using any of the QColor constructor methods
customdash
Length and spacing of dash, separated by a semi-colon; e.g. 8;4
customdash_unit
MM or MapUnit
joinstyle
’bevel’, ’miter’, or ’round’
offset
Specified as an x,y integer offset
offset_unit
MM or MapUnit
penstyle
’no’, ’solid’, ’dash’, ’dot’, ’dash dot’, ’dash dot dot’
use_custom_dash
1 to use the custom dash
width
Integer value
width_unit
MM or MapUnit
border_width_unit
MM or MapUnit
color
Specified using any of the QColor constructor methods
color_border
Specified using any of the QColor constructor methods
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 95
offset
Specified as an x,y integer offset
offset_unit
MM or MapUnit
style
’solid’, ’horizontal’, ’vertical’, ’cross’, ’b_diagonal’, ’f_diagonal’, ’di-
agonal_x’, ’dense1’, ’dense2’, ’dense3’, ’dense4’, ’dense5’, ’dense6’,
’dense7’
style_border
’no’, ’solid’, ’dash’, ’dot’, ’dash dot’, ’dash dot dot’
width_border
Integer value
For example, to create a blue circle marker with a size of 8 and outline width
of 2 we would use:
sym = QgsMarkerSymbolV2.createSimple({
'name':'circle',
'color':'blue',
'size':'8',
'outline_width':'2'
})
renderer = layer.rendererV2()
renderer.setSymbol(sym)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
96 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
})
renderer = layer.rendererV2()
renderer.setSymbol(sym)
After setting the symbol, we must refresh the map canvas and legend for the
changes to be visible.
There are also properties that can be set to create a symbol using an ex-
pression that references data defined values. The keywords for each symbol
types are as follows:
QgsMarkerSymbolV2:
angle_expression
color_border_expression
color_expression
horizontal_anchor_point_expression
name_expression
offset_expression
outline_width_expression
size_expression
vertical_anchor_point_expression
QgsLineSymbolV2:
color_expression
width_expression
offset_expression
customdash_expression
joinstyle_expression
capstyle_expression
QgsFillSymbolV2:
color_expression
color_border_expresssion
width_border_expression
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 4 . WO R K I N G W I T H S Y M B O L O G Y 97
These expressions are similar to what you would set using the Data de-
fined properties button on the Layer properties dialog. For example, here is
how to create a simple marker that uses the MARKER_SIZE field in a layer’s
attribute table to set the symbol size.
sym = QgsMarkerSymbolV2.createSimple({
'name':'circle',
'color':'blue',
'size_expression':'MARKER_SIZE',
'outline_width':'1'
})
renderer = layer.rendererV2()
renderer.setSymbol(sym)
Symbol Layers
In QGIS, a symbol can have multiple layers. This allows you to create
interesting symbology, such as a black highway with striping:
This symbol is composed of four separate layers that are overlaid to create
the appearance of a striped highway. Let’s use the console to take a look at
the symbol:
>>> lyr = iface.activeLayer()
>>> renderer = lyr.rendererV2()
>>> symbol = renderer.symbol()
>>> symbol.symbolLayerCount()
4
Here we confirmed there are four layers in the symbol by using the sym-
bolLayerCount method once we fetched the symbol from the layer’s ren-
derer. We can access individual layers of the symbol using the symbolLayer
method:
>>> sym0 = symbol.symbolLayer(0)
>>> sym0
<qgis.core.QgsSimpleLineSymbolLayerV2 object at 0x121ed89e0>
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
98 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Once we have the symbol layer , we can make changes to it. Lets change the
dash pattern of the center line of the highway. Obviously we have to have
some knowledge about the layout of the symbol layers in order to select the
one we want to change. The ordering of the layers are from the bottom up,
starting with zero. Our center line is the top layer, so we fetch it using and
index of 3:
>>> sym3 = symbol.symbolLayer(3)
Using the symbol layer methods allow you to customize symbology for
layers on your map. If you look at the available methods, you’ll notice that
we also have access to data defined properties and can tweak them in our
code as well.
For working with fill and marker symbol layers, see the API documentation
for QgsSimpleFillSymbolLayerV2 and QgsSimpleMarkerSymbolLayerV2.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 5 . S E L E C T I N G A N D WO R K I N G W I T H F E AT U R E S 99
Using Styles
• saveNamedStyle
• loadNamedStyle
• saveDefaultStyle
• loadDefaultStyle
• saveSldStyle
• loadSldStyle
For example, you can symbolize a layer the way you want it, then save the
style information to a file:
layer = iface.activeLayer()
layer.saveNamedStyle('/tmp/mystyle.qml')
Note we have to refresh both the canvas and the legend in order to see the
applied style. Using the saveSldStyle/loadSldStyle methods do the same
thing, but save the style as a Styled Layer Descriptor file.
Using saveDefaultStyle creates a qml file on disk that will be applied each
time the layer is loaded. For example, if we load /gis_data/alaska.shp,
style it, and use saveDefaultStyle, it creates /gis_data/alaska.qml. The
next time we load alaska.shp, the style will be automatically applied.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
100 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
We can access the attributes of a feature using either an index or the name.
For the world_borders layer we can get the country name using either of
these two methods:
name = feature['CNTRY_NAME']
name = feature[2]
Note we can use either upper or lower case and QGIS will recognize the
field name.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 6 . E D I T I N G AT T R I BU T E S 101
layer.selectAll()
Direct editing is easier, but doesn’t allow for a rollback of changes. For
the next two examples, assume we have a layer with the following fields:
name and city.
If we know the feature id, we can update the name without even entering
edit mode:
fid = 1
new_name = { 2: 'My New Name'}
layer.dataProvider().changeAttributeValues({fid: new_name})
To update the name field, we create a dict (new_name) using the field index
as the key, followed by the new name. When we call changeAttributeValues,
the name field (index of 2) for feature with id of 1 is updated. This requires
knowing the field index in advance. We can also get the field index number
by first fetching the feature we want to modify:
features = layer.getFeatures(QgsFeatureRequest().setFilterFid(1))
feature = features.next()
new_name = { feature.fieldNameIndex('name'): 'My New Name'}
layer.dataProvider().changeAttributeValues({feature.id(): new_name})
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
102 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
We can also update multiple attributes and save them using a dict compre-
hension:
1 layer = iface.activeLayer()
2 provider = layer.dataProvider()
3 features = layer.getFeatures(QgsFeatureRequest().setFilterFid(1))
4 feature = features.next()
5 feature['name'] = 'My New Name'
6 feature['city'] = 'Seattle'
7 field_map = provider.fieldNameMap()
8 attrs = {field_map[key]: feature[key] for key in field_map}
9 layer.dataProvider().changeAttributeValues({feature.id(): attrs})
The advantage here is a simpler syntax for assigning new values and ease
in building up the dict needed by changeAttributeValues.
Using an edit buffer is best suited to situations where you are providing
a custom editing experience via a GUI. This gives the user the chance to
rollback or cancel changes. For an example of using an editing buffer, see
the PyQGIS Cookbook.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 8 . G E T T I N G Q G I S PAT H S 103
• BMP
• JPG
• JPEG
• PNG
• PPM
• XBM
• XPM
A world file is also created in the same location as your image. This allows
you to later add the image to QGIS as a georeferenced raster.
From the console, we can get a summary of the paths used in QGIS using
showSettings:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
104 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
print QgsApplication.showSettings()
Application state:
QGIS_PREFIX_PATH env var:
Prefix: /dev1/apps/qgis
Plugin Path: /dev1/apps/qgis/lib/qgis/plugins
Package Data Path: /dev1/apps/qgis/share/qgis
Active Theme Name: default
Active Theme Path: :/images/themes/default/
Default Theme Path: :/images/themes/default/
SVG Search Paths: /dev1/apps/qgis/share/qgis/svg/
/home/gsherman/.qgis2//svg/
User DB Path: /dev1/apps/qgis/share/qgis/resources/qgis.db
Often you need to know where your plugin is installed, so you can access
additional resources such as templates and help files. There is more than
one way to get the path to your plugin, but the surest way is to use QgsAp-
plication.qgisUserDbFilePath:
>>> QgsApplication.qgisUserDbFilePath()
u'/Users/gsherman/.qgis2//qgis.db'
This gives us the path to the qgis.db that is created for each user. Earlier
we talked about where plugins are stored and learned, regardless of operat-
ing system, the .qgis2/python/plugins portion of the path is the same.
os.path.join assembles path compo- Knowing this, we can create the full path to where our plugin is installed
nents in an intelligent, OS compatible
using os.path.join and QFileInfo:
way.
>>> QFileInfo(QgsApplication.qgisUserDbFilePath()).path()
u'/Users/gsherman/.qgis2/'
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 9 . M E S S AG E S A N D F E E D BAC K 105
Using QMessageBox
Using a popup message box is a valid way to provide feedback or infor- We saw an example of QMessageBox
in Section 5.4, Static Public Member
mation. The downside is, it can get in the way of what you’re trying to
Functions, on page 55.
accomplish, particularly when presenting just a bit of information.
• QMessageBox.information
• QMessageBox.warning
• QMessageBox.critical
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
106 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
There are some optional arguments we can supply, however for a simple
popup message, the functions are called like this:
QMessageBox.information(parent, title, text)
The parent argument is the GUI element that “owns” the message box. Typ-
ically you can specify None and it will work fine. To make the box a child
of the main QGIS window, you can use iface.mainwWindow():
QMessageBox.information(iface.mainwWindow(),
'Important Information',
'This is an important message')
Using QgsMessageLog
QGIS logs messages to a special panel. You can access the message panel
from the View->Panels->Log Messages menu or by clicking on the yel-
low triangle icon at the bottom right of the status bar. The messages are
organized by tabs that indicate the source of the message.
To log a message to your own custom tab, use something like this:
QgsMessageLog.logMessage('SuperZoom plugin intialized and ready',
'SuperZoom',
QgsMessageLog.INFO)
The first argument is the message, the second the tag that will be used as
the tab name, and the last is the message level.
You can see from Figure 8.2, on the facing page that entering the above
example from the console creates a new tab named SuperZoom and writes
the message and level to it.
Using QgsMessageBar
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 9 . M E S S AG E S A N D F E E D BAC K 107
iface.messageBar().pushMessage("Title","Message", QgsMessageBar.WARNING, 2)
This will place a message bar at the top of the map canvas. The message
consists of a title, the message, and a button to dismiss the message. The
look of the message is controlled by the level argument and the duration in
seconds is specified by the last argument.
Figure 8.3, on the next page shows the result when we enter the following
in the console:
iface.messageBar().pushMessage("SuperZoom",
"You specified an invalid zoom level",
QgsMessageBar.CRITICAL,
10)
The message has a red background, a “critical” icon, and stays on screen
for 10 seconds.
If you notice the console commands in Figure 8.3, on the following page,
we imported the QgsMessageBar class prior to displaying the message bar:
from qgis.gui import QgsMessageBar
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
108 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
Any time you get a NameError when working with the API, it means
you need to import the module containing the object you are referencing.
This only needs to be done once in a script or console session.
These are the C++ definitions, but we won’t let that bother us. The first
implementation supports only a message, whereas the second supports a
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8.10. REFRESHING THE MAP AND LEGEND 109
title and message. Both methods include the level and duration. When you
see an equal sign (=) in a method definition it specifies a default value. In
the case of pushMessage, the level defaults to INFO and the duration to 0,
meaning the message will remain until the user closes it. You can actually
create an INFO message using a single argument:
iface.messageBar().pushMessage("You specified an invalid zoom level")
To implement these features we need the following methods from the QgsMap-
ToolEmitPoint base class:
• canvasPressEvent
• canvasMoveEvent
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
110 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
First we need some of the usual imports (lines 1-4), along with one we
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 1 . C R E AT I N G A M A P TO O L 111
In line 7 we see the class declaration for ConnectTool and note that it sub-
classes QgsMapToolEmitPoint. As we said, this gives us all the functional-
ity of the base class, along with the ability to override it to customize the
behavior.
We also declare and set our start, end, and rubberband attributes to None
in lines 11-13.
Next our __init__ method (lines 15-17) sets things up, including storing a
reference to the map canvas (which we must pass to it). You’ll notice on line
17 that we are calling the init method of the parent class. This is important
to get things initialized properly.
We need a starting point for our rubberband; we check for that in line 20,
and if it exists, we get the click point in line 21. Next we check to see if
we have a rubberband object in line 22. If we do, we reset it (because it is
going to change), otherwise we create the rubberband and set its color to
red (lines 24-26).
To set the rubberband’s geometry, we use the start_point and the current
location of the mouse cursor, as contained in the event. Line 28 creates
the list needed to set the geometry, which is done in lines 29 and 30. This
will cause the rubberband to appear on the canvas. As we move the mouse,
canvasMoveEvent is called repeatedly and the rubberband’s geometry is up-
dated, making it appear to move with the cursor.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
112 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
The last method from our base class that we will override is the canvasPres-
sEvent (lines 32-43). This event is fired whenever there is a mouse click on
the map canvas and we use it to set our start and end points. The event in-
cludes information about the location of the click, as well as which button
was used and any keyboard modifiers in play. For our purposes, we’re only
interested in two things: the fact that a mouse button was clicked and the
location of the click.
In line 33 we check to see if we don’t yet have a start point and if so, set
it in line 34. Otherwise, the click is our end point and we set it, reset the
rubberband, emit the line_complete signal, then clear our start/end points
(lines 35-43).
In line 40 we emit our custom signal. The signal, along the two arguments
(start and end points) is emitted and any class or method that has connected
to the signal will catch it. The start and end points can then be used to create
a new feature or do something else useful.
To use our new map tool we’ll create a class that implements the map tool
and creates the line based on the two points clicked:
1. Create it:
map_tool = ConnectTool(self.canvas)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 2 . A D D I N G E X I ST I N G TO O L S TO A C U S TO M TO O L BA R 113
self.connect_action.triggered.connect(self.connect_pt)
For example, let’s say we have a toolbar in our plugin class referenced as
self.toolbar and our plugin also has stashed the qgis.utils.iface ob-
ject in self.iface. Here is how we would add the select by rectangle tool
to our toolbar in the initialization of the GUI:
rect_select = self.iface.actionSelectRectangle()
self.toolbar.addAction(rect_select)
That’s all there is to it. The action is added to our toolbar and shows up with
the same icon as in the QGIS Manage Layers toolbar. The tool is ready to
use and performs exactly like the original.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
114 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
• create_pin_layer()
• place_pin(point, button)
The first method creates a memory layer for the pins and the second method
places the pin based on the location of the user click. In order to use the
methods of the PinPoint plugin, we first need to make sure it is loaded and
active. The qgis.utils.plugins dict contains all loaded plugins, keyed
by name. We can access PinPoint as follows:
>>> pp = qgis.utils.plugins['pinpoint']
>>> pp
<pinpoint.pinpoint.PinPoint instance at 0x9efba4c>
While this works when PinPoint is loaded, we don’t fare so well when it’s
not:
pp = qgis.utils.plugins['pinpoint']
Traceback (most recent call last):
File "<input>", line 1, in <module>
KeyError: 'pinpoint'
Now that we know how to get a reference to the PinPoint plugin, here’s the
code we would use to create the pin layer and place a pin at coordinate 100,
100:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8 . 1 4 . S E T T I N G U P A RE P O S I TO RY 115
if 'pinpoint' in qgis.utils.plugins:
pp = qgis.utils.plugins['pinpoint']
pp.create_pin_layer()
pp.place_pin(QgsPoint(100,100), 1)
The call to place_pin will popup a dialog so we can enter the name for the
pin and when we click OK, the pin is placed on our newly created memory
layer.
2. Uploading the plugin zip file to a web directory for download by the
QGIS Plugin Manager
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
116 C H A P T E R 8 . T I P S A N D T E C H N I QU E S
13 <create_date>2013-07-05</create_date>
14 <update_date>None</update_date>
15 <experimental>False</experimental>
16 </pyqgis_plugin>
17 </plugins>
This example defines one plugin: ScriptRunner. Each plugin in the repos-
itory is defined within a set of pyqgis_plugin tags. The requirements are
pretty much self-explanatory. In order for QGIS to be able to fetch and in-
stall the plugin, the zip file must be correctly specified in the download_url
tag.
Once you have setup the repository, add it to the Plugin Manager using the
Settings tab (see Figure 8.4).
When Plugin Manager fetches from the configured repositories, your plu-
gin(s) will show up in the list of available plugins.
Next up, we’ll look at extending the API, then move on to writing a plugin.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
8.15. EXERCISES 117
8.15 Exercises
1. Find the qgis.utils module in your QGIS install and open it in a
text editor. Examine the methods and data structures available.
2. Create a point memory layer with an id and name field, add some
features to it, and add it to the map canvas.
3. Write a script to turn on labeling for the point layer you created and
label each feature using the name field.
4. Using the point layer, write a script that allows you to edit the name
for a selected feature by prompting for the new name, then saving it
to the attribute table.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9
Extending the API
In this chapter, we will work up a little “wrapper” script that makes working
with layers and colors a bit easier. Our goal here is to simplify the adding
of vector and raster layers. In addition, we’ll write a function to simplify
changing the color of a layer and updating the legend.
We need the PyQt4.QtGui module because we are going to use the QColor
As you look through the listings, think class to set the color for our vector layers. We’ll also use some classes from
of ways you could refactor the code to
the qgis.core and qgis.iface modules. Lastly, the ogr and gdal mod-
make it more concise.
ules are needed so we can identify what type of layer we are adding—this
will become clear in the next couple of code snippets.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 3 . T H E A D D O G R L AY E R A N D A D D G DA L L AY E R F U N C T I O N S 121
In lines 35-37 we check to see if a custom name for the layer was passed.
If so we use it, otherwise the filename is used in the legend.
In line 39 the layer is created by passing the path, name, and the data
provider key (’ogr’) to QgsVectorLayer. In line 40 we add it to the map
using the addMapLayer method of the QgsMapLayerRegistry class and re-
turn a reference to the layer.
Adding a raster works in much the same way in lines 43-50, passing the
path and name to QgsRasterLayer, adding it to the map, and returning a
reference to it.
Let’s see how we would use what we have so far to add both a vector and
raster layer to the map.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
122 CHAPTER 9. EXTENDING THE API
The last method might seem easy, but when you upgrade or uninstall QGIS,
your script may be lost.
Reminder: the code is available from In our example, we’ll use the second method just to illustrate how it’s done.
http://locatepress.com/files/
We extracted pyqgis_code.zip to our development directory:
pyqgis_code.zip.
/home/gsherman/development/pyqgis_code
Here is the script that allows us to add the world_borders vector layer and
the natural_earth raster to our map canvas using wrapper.py:
1 import sys
2 sys.path.append('/home/gsherman/development/pyqgis_code')
3 from wrapper import wrapper
4 lyr_vector = wrapper.addLayer('/data/world_borders.shp', 'World Borders')
5 lyr_raster = wrapper.addLayer('/data/HYP_50M_SR_W.tif', 'Natural Earth')
In line 2 we add the directory containing the wrapper module to the Python
path by hardcoding it.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 4 . U S I N G T H E A D D L AY E R F U N C T I O N 123
If you have a script or plugin that needs to import additional modules located
in the same directory, you can do so by adding the following statements to
your code:
import os
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
This adds the directory where your script resides (contained in __file__) to
the Python path in a fairly portable manner.
We can load our little script in the Console Editor and run it using the Run
script button. The result can be seen in Figure 9.1. Notice the order in
which we executed our statements resulted in the raster being loaded over
the vector layer. You should now realize that using addLayer is much easier
than the step-by-step way of loading a layer.23 23
Well maybe not a lot easier, but cer-
tainly lazier.
Let’s make it easy to remove a layer—in this case we’ll use it to remove
the natural_earth layer so we can see the world_borders. Here’s the
function we need to add to wrapper.py:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
124 CHAPTER 9. EXTENDING THE API
The removeLayer function uses the layer’s id to remove it from the map.
While we can add layers without storing a reference to them, doing so
prevents us from working further with them. In our test script we stored
references to the vector and raster layers in lyr_vector and lyr_raster
respectively.
Running a script from the Console Editor also makes the variables and
objects accessible in the console. For example, you can run the script to add
vector and raster layers from the editor and then switch to the console and
interactively use the wrapper.removeLayer function to remove a layer.
Here’s our wrapper script so far—next we’ll add a couple functions to help
us change the color and transparency of a vector layer:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 5 . C H A N G I N G T H E C O L O R A N D T R A N S PA R E N C Y O F A V E C TO R L AY E R 125
The createRGBA function accepts a color definition string using four, comma
separated integers ranging from 0 to 255 that represent the red, green, blue,
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
126 CHAPTER 9. EXTENDING THE API
For example, to create a red color with 50% transparency, we would use:
wrapper.createRGBA('255, 0, 0, 128')
Line 59 creates the color object using QColor.fromRgb and returns it.
Now that we can create a custom RGBA color, let’s look at the function that
actually changes the color of a vector layer on both the map canvas and in
the legend:
You may remember that we took a look at the ways in which we can cre-
ate a QColor object back in Section 6.4, Exploring Vector Symbology, on
page 68. There we saw how to create a QColor object using:
• QColor(Qt.red)
• QColor(’red’)
• QColor(’#ff0000’)
• QColor(255,0,0,255)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9 . 5 . C H A N G I N G T H E C O L O R A N D T R A N S PA R E N C Y O F A V E C TO R L AY E R 127
In lines 65-69 we check to see if an RGBA string has been passed in the
color parameter and if so, we create the color using createRGBA, otherwise
we create the color directly.
With the color created, we get a reference to the symbol for the layer and
set it in lines 71-73.
The last thing to do is refresh the layer and the legend; this is done using
refresh in line 75 and refreshLayerSymbology in line 76. Notice also we
invalidated the image cache in line 74 just in case it exists—this ensures the
repaint will succeed.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
128 CHAPTER 9. EXTENDING THE API
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
9.6. EXERCISES 129
78 layer.setCacheImage(None)
79 iface.mapCanvas().refresh()
80 iface.legendInterface().refreshLayerSymbology(layer)
This chapter gives you a simple example of how you can wrap up various
QGIS API classes and methods to create new functions. You may have
already thought of enhancements that could be implemented in the wrapper.
Some suggestions follow in the next section.
9.6 Exercises
1. Identify areas in wrapper.py that are potential failure points and add
appropriate error checks. For example, what happens if you pass an
invalid color specification to changeColor?
2. Add a function to reorder the layers in the legend and test it by load-
ing world_borders.shp, natural_earth.tif, and then switching
their order.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10
Writing Plugins
In this chapter we dive into creating a simple plugin from scratch, but first
we need to say a few words about the plugin architecture in QGIS.
testplugin
Top-level directory of the plugin.
132 CHAPTER 10. WRITING PLUGINS
__init__.py
This script contains one method (classFactory) that initializes the plugin
class and makes it known to QGIS.
icon.png
The icon to be used for the plugin when displayed on a QGIS toolbar.
The icon should be 24x24 pixels in PNG format.
metadata.txt
The metadata file contains information about the plugin, including the
name, description, version, icon, and minimum QGIS version. This file
is necessary for QGIS to recognize the plugin.
resources.qrc
Describes resources (e.g. icon.png), used by the plugin and its GUI
forms.
resources_rc.py
The Python file generated from resources.py by the PyQt resource
compiler, pyrcc4
testplugin.py
The main implementation of your plugin that handles loading, unload-
ing, and execution of the plugin’s functions.
testplugindialog.py
The main GUI dialog for the plugin.
ui_testplugin.py
The Python file generated from ui_testplugin.ui by the PyQt inter-
face compiler, pyuic4.
ui_testplugin.ui
The GUI interface file created by Qt Designer.
We’ll look at the details of these plugin components, as well as some addi-
tional ones shortly.
Once you have a plugin that works locally, you can package it up in a num-
ber of ways. The steps to do it manually are as follows:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.1. PYTHON PLUGIN ARCHITECTURE 133
2. Copy only the necessary files for your plugin to the distribution di-
rectory. If you are using a version control system (VCS) during devel-
opment, you should do an export to avoid distributing files associated
with your VCS.
3. Package the plugin by making a zip archive of it, including the direc-
tory
The zip file is now ready for upload to the QGIS repository, but you should
probably test it by unzipping into the location of your QGIS plugins to make
sure it loads/unloads properly.
Along with the command line, you can use any tool you like to create the
archive, including popular zip managers on Windows. The important point
is to use zip to package the file and be sure to include the directory in the
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
134 CHAPTER 10. WRITING PLUGINS
archive.
When QGIS starts up, it examines the contents of your plugin directory to
create a list of all valid plugins. Each plugin you have previously enabled is
then started by calling the classFactory method in __init__.py. If the plugin
starts successfully, the initGui method is called to add entries to the menu
and toolbars. The plugin is then added to the list of active plugins.
Any failure during the startup process will result in an exception and an
error message will be displayed in QGIS. These errors can be helpful when
developing a plugin as they show the file name and line number of the error.
Before you protest, it’s true we can see the mouse coordinates in the QGIS
status bar. We can even tab to the box where they are displayed and copy
them but that’s not the point—we want to implement it as a plugin to form
the basis for a more advance functionality later. We’ll start with the Plugin
Builder.
Back in the old days (around QGIS version 0.9) we had to create all the
boilerplate for a Python plugin by hand. This was tedious and basically
the same for each plugin. Fortunately that’s no longer the case—we can
generate a plugin template using the Plugin Builder.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 3 . C R E AT I N G A S I M P L E P L U G I N 135
The Plugin Builder is itself a Python plugin that takes some input from you
and creates all the files needed for a new plugin. It’s then up to you to
customize things and add the code to do something useful. If you did the
exercises back in Chapter 4, The QGIS/Python Ecosystem, on page 41, you
already have Plugin Builder installed. If not, install it now using the Plugin
Manager by clicking on the Plugins->Manage and Install Plugins
menu.
Let’s generate the structure for our Where Am I? plugin by clicking on the
Plugin Builder tool or menu item. We are presented with a dialog that
contains all the fields needed to create the plugin. On the left side of the
plugin dialog you’ll see some hints about what is expected for each field.
Figure 10.1, on the next page shows all the fields needed to generate the
plugin.
When we click OK, the Plugin Builder generates the files needed for your
plugin. In addition to the ones we saw in Section 10.1, Python Plugin Ar-
chitecture, on page 131, several additional files have been created for us:
Makefile
This is a GNU makefile that can be used to compile the resource file
resources.qrc and the user interface file (.ui). This requires gmake
and works on both Linux and Mac OS X and should also work with the
OSGeo4W shell on Windows.
help
This directory contains the files needed to begin documenting your plu-
gin using Sphinx. Sphinx is a Python documentation
generator available at http://sphinx-
i18n doc.org.
Empty directory to be used for creating translations of your plugin.
plugin_upload.py
A Python script to upload the plugin to the QGIS plugin repository. Typ-
ically, you would use the web interface at http://plugins.qgis.org
instead of this script.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
136 CHAPTER 10. WRITING PLUGINS
After the plugin generates the needed files, a results dialog is shown that
contains some helpful information, as shown in Figure 10.2, on the next
page.
You’ll notice the naming of a number of the files is based on a lower case
version of the name you provide for your plugin, in this case whereami.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 3 . C R E AT I N G A S I M P L E P L U G I N 137
Our generated plugin is almost fully functional. The only thing we need to
do is compile the resource and user interface files, then place whereami in
our plugin directory:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
138 CHAPTER 10. WRITING PLUGINS
4. Copy the entire whereami plugin directory to your QGIS plugins di-
rectory. If you need a reminder, the location as listed in both the
README files.
Now, start QGIS and open the Plugin Manager—you should see Where Am
Plugin Manager lists plugins by their I in the list of installed plugins. Click the checkbox next to it to enable it
description, not the plugin name.
and then click OK. You should now find a new icon on the Plugins toolbar,
as well as a menu entry in Plugins->Where Am I?.
Clicking on the tool or the menu item brings up the plugin as shown in
Figure 10.3.
It’s not much to look at yet, but it is fully functional in the following ways:
We’ll talk more about the accept() and reject() signals later on.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 4 . C U S TO M I Z I N G T H E I C O N 139
We also need to tweak the GUI to add a single box where we will report the
coordinates of the map click.
This is fine for getting started, but you really don’t want your plugin using
the same icon as dozens of others. You have two options for changing the
icon:
Of course, you don’t have to change the icon during development—the de-
fault created by Plugin Builder works fine. For our example, here is a simple
“question mark” icon I created using Gimp24 : 24
http://gimp.org
Now all we need to do is to modify the resources file to use our new icon.
This resource file uses a prefix to prevent naming clashes with other plugins.
It’s good to make sure your prefix will be unique—usually using the name
of your plugin is adequate (Plugin Builder created the prefix for you based
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
140 CHAPTER 10. WRITING PLUGINS
on the plugin name). To add our customized icon, we need to replace the
generated file name (icon.png) with our customized icon, which I named
whereami.png:
<RCC>
<qresource prefix="/plugins/whereami" >
<file>whereami.png</file>
</qresource>
</RCC>
Since we are using a new file name for our icon, we need to make a couple
of changes in our code, specifically in metadata.txt and whereami.py.
In whereami.py, the icon is loaded from the graphic file specified in line 4:
1 def initGui(self):
2 # Create action that will start plugin configuration
3 self.action = QAction(
4 QIcon(":/plugins/whereami/icon.png"),
5 u"Where Am I?", self.iface.mainWindow())
6 ...
With the changes complete and the resource file saved, we need to compile
it in order for it to be used by our new plugin:
pyrcc4 -o resources.py resources.qrc
The -o switch is used to define the output file. If you don’t include it, the
output of pyrcc4 will be written to the terminal. Now that we have the re-
sources compiled, we need to build the GUI to display the map coordinates
when we click on a point.
If you choose to use the default icon created by Plugin Builder you don’t
have to modify the resources file, but you do have to compile it.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 5 . C U S TO M I Z I N G T H E G U I 141
To fix up our GUI, we’ll use the same tool that the QGIS C++ developers Designer is now part of Qt Cre-
ator. See http://loc8.cc/ppg/
use: Qt Designer. This is a visual design tool that allows you to create dialog
qtcreator.
boxes and main windows by dragging and dropping widgets and defining
their properties. Designer is normally installed along with Qt, so it should
be already available on your machine.25 25
On some Linux distributions you
may have to install additional pack-
Let’s take a look at what it takes to polish up our GUI for WhereAmI. This ages using your package manager.
To begin, we open our generated dialog box in Designer using the File
menu and selecting ui_whereami.ui.
In Figure 10.4, on the following page, you can see the dialog box as gener-
ated by Plugin Builder in Designer, along with the widget palette and the
property editor.
2. Drag and drop a Label widget (found under Display Widgets) to the
dialog box
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
142 CHAPTER 10. WRITING PLUGINS
4. Drag and drop a Line Edit widget (found under Input Widgets) under
the label and resize it to near the width of the dialog box
5. Drag and drop a Push Button widget (found under Buttons) on the
dialog box just under the Line Edit widget
Our dialog is complete with one exception—the Close button is not hooked
up to do anything when clicked. By default, the Close/OK button group is
wired up to manage accepting (OK) and rejecting (Close) the dialog. We
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 0 . 5 . C U S TO M I Z I N G T H E G U I 143
could have used the button group by deleting just the OK button, however
to illustrate wiring up a button to a method we’ll do it by hand.
To make our new Close button work, we use the Signal/Slot editor in Designer:
2. Click and hold on the Close button, then drag the mouse cursor to the
empty space on the dialog box and release the mouse to bring up the
Configure Connection box
Figure 10.5, on the next page shows the connection made between the but-
ton’s pressed signal and the dialog’s reject slot. This means that, when
the button is pressed, the dialog receives the signal and directs it to the reject
slot (which is really just a method), and closes the dialog.
You don’t need to know the details of how the signal/slot mechanism in Qt
works to create simple dialogs like the one for the WhereAmI plugin. As
you develop more sophisticated PyQGIS plugins or applications, you will
want to delve into it a bit more. We will be making connections manually
when we add some code to the WhereAmI plugin.
Once we have all the controls on the form, we’re ready to generate some
code from it. To convert our completed dialog box to Python, we use the
PyQt pyuic4 command to compile it:
pyuic4 -o ui_whereami.py ui_whereami.ui
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
144 CHAPTER 10. WRITING PLUGINS
If the compiled dialog is not named properly the plugin will fail to initialize.
First let’s look at the code generated for us by Plugin Builder, starting
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.6. WRITING THE PLUGIN CODE 145
with the imports and the __init__ method in the main Python source file,
whereami.py:
In lines 1-3, we import the PyQt libraries and the QGIS core library. Line 5
imports our resources file and in line 7, the code needed to load and initialize
our GUI dialog is imported. This code is all generated by Plugin Builder.
If you need additional Python modules for your plugin, you’ll add them in
this section of code—for WhereAmI we don’t need anything further.
Line 10 starts the class definition of WhereAmI, the __init__ method being
defined first. You’ll see some of housekeeping items are taken care of in
lines 13-20. Again, we don’t need to change anything in these lines of
code. We do however, have to add some additional code to make our plugin
work with the map canvas.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
146 CHAPTER 10. WRITING PLUGINS
The first step is to create the map tool using the QgsMapToolEmitPoint class
from the QGIS API. This class implements a map tool that, when clicked,
emits a point containing the map coordinates. To create the tool and store it
as an attribute of the WhereAmI class we need to import it by adding a new
statement to our imports:
from qgis.gui import QgsMapToolEmitPoint
With these changes, the first section of our code now looks like this, with
additions at line 5 and lines 24-27:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.6. WRITING THE PLUGIN CODE 147
Let’s look at using the old method to connect a click (the canvasClicked
signal) of our tool to our display method:
# connect signal that the canvas was clicked
result = QObject.connect(self.pointTool,
SIGNAL("canvasClicked(const QgsPoint &, Qt::MouseButton)"),
self.display_point)
Using the old method requires us to know how to write the arguments in the
proper form. The new method is much simpler and we’ll use it:
# connect signal that the canvas was clicked
self.pointTool.canvasClicked.connect(self.display_point)
Now when the WhereAmI tool is selected and the map canvas is clicked, the
framework will capture the click and the coordinates (as a QgsPoint object),
and pass control off to our as yet unwritten display_point method.
We’ll go back and put this all together shortly, but let’s look at the dis-
play_point method first:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
148 CHAPTER 10. WRITING PLUGINS
This tells us that our display_point method receives a QgsPoint object con-
taining the coordinates of the map click, and a button value indicating which
button was pushed. Knowing which mouse button was pressed allows us to
perform different actions based on how you click on the map. In our simple
plugin, we don’t care how the click is generated—we just want to display
the coordinates.
In line 3 we hide the dialog so when we show it again it will pop up on top
of the main window, otherwise subsequent clicks would result in our dialog
always being hidden.
Line 4 creates a string using a format specification and the X and Y values
from the QgsPoint object using the x() and y() methods. This results in a
formatted string with lots of decimal places:
-152.661636888, 65.6374837099
As of Plugin Builder 2.0.3, the gen- Line 5 sets the value of the line edit box in our dialog to our formatted result
erated dialog class inherits from both
string. To refer to any GUI element we prefix the name with self.dlg.ui.
QDialog and the UI class. To re-
fer to any GUI element, you now use For example, to refer to the label, we would use self.dlg.ui.label. If
self.dlg.elementName rather than you’re wondering how we know it’s label, the names of all GUI elements
self.dlg.ui.elementName.
on the dialog can be viewed in the Object Inspector in Designer, as seen in
Figure 10.5, on page 144.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.7. ONE LAST TWEAK 149
First let’s look at the dialog code needed to implement the change:
We added line 13 to initialize an attribute that will store the position of the
result dialog, setting it to None, the Python way of expressing “absence of
value.” In other words, we haven’t set self.userPos to anything yet—that
happens in the moveEvent method on lines 15 and 16.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
150 CHAPTER 10. WRITING PLUGINS
In lines 7 and 8 we check to see if the user position has been set and, if so,
move the dialog before showing it.
If you try the plugin, you’ll notice that you can move the dialog to a con-
venient position after the first use and it will stay there with all subsequent
uses during your QGIS session. Since we didn’t write any code to persist
the settings, when you exit QGIS, the dialog position is lost.
Figure 10.6 shows the result of using the WhereAmI plugin. Note the results
in the plugin match those displayed in the coordinate box of the QGIS status
bar.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
10.8. EXERCISES 151
Now that we’ve written a simple plugin, in the next chapter we’ll talk a bit
about developing an efficient development workflow.
10.8 Exercises
1. Format the results of the WhereAmI plugin so only three decimal
places are shown.
3. Add a button to the right of the Label or Line Edit widget that copies
the results to the clipboard when clicked (hint: see the QClipboard
class in the Qt documentation).
4. Add a layout to the plugin dialog box so when it is resized, the wid-
gets resize appropriately (hint: use the layout tools in Designer).
5. Using QSettings and the closeEvent of the dialog, save the dialog
position and restore it each time the plugin is loaded.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
11
Creating a Development Workflow
Now that we have developed a simple plugin, let’s talk a bit about the de-
velopment process and how to establish a workflow that suits you best.
find it in order to test it. Let’s examine the options for setting up a workflow.
1. Copy or move the plugin files to your QGIS plugin directory and
develop from there.
5. Use git to commit your changes then pull to your QGIS plugin di-
rectory each time you want to test
This method (option one above) is very convenient as long as you don’t test
the uninstall feature of your plugin. If you do, the Plugin Manager will
warn you, yet happily delete your entire plugin directory if you click the
Yes button, destroying all your source code in the process—not really what
we want.
This method is safer than developing directly in your plugin directory, how-
ever to test your plugin you have to copy from your development directory
to your QGIS plugin directory every time you want to test. If you are on
a Unix based system or are using the OSGeo4W install of QGIS, you can
28
Plugin Builder creates a Makefile create a Makefile to deploy the plugin for you.28
you can customize for building and de-
ploying your plugin. Using the QGIS_PLUGINPATH Environment Variable
With this method (option three), you work with your code in a separate
directory but use the QGIS_PLUGINPATH environment variable to point to
your development directory. When present, QGIS_PLUGINPATH tells QGIS
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 1 . 1 . C H O OS I N G A D E V E L O P M E N T M E T H O D O L O G Y 155
to search additional directories for plugins. Going this route allows you
to develop in the directory created by Plugin Builder and test your plugin
without any copying or pulling. It is still possible to uninstall your plu-
gin (and destroy your source code) using Plugin Manager if you miss and
accidentally click the Yes button.
This method (option four) can be useful but requires a bit of work upfront.
You setup a plugin repository (see Section 8.14, Setting Up a Repository,
on page 115), then deploy your plugin to it. You can then install onto any
machine for testing. I use this method when testing the cross-platform com-
patibility of a plugin, usually during the latter stages of development.
Using git
The last option in our list of suggested methodologies is to use git to pull
changes into your QGIS plugin directory. The steps to use this method are
roughly:
1. Create your plugin in any source directory you desire (preferably us-
ing Plugin Builder)
4. Develop your plugin and commit the changes using git commit
5. Change to your QGIS plugin directory (see Section 4.6, Python Plu-
gin Specifics, on page 46)
6. Clone your source repository that contains the plugin. For example,
if your source code is in /home/myname/myplugin:
git clone /home/myname/myplugin
8. Make more edits in the source directory and commit, then change to
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
156 C H A P T E R 1 1 . C R E AT I N G A D E V E L O P M E N T WO R K F L OW
Regardless of which method you use for development, keeping your source
code under version control and pushed to an off-site repository (such as
Github or BitBucket) is a good idea.
So which method should you use? This of course is a matter of opinion, but
here is one alternative; a workflow which combines a few of the options we
discussed above:
The Plugin Reloader plugin can be very useful when developing your
plugin. It allows you to reload your plugin after changes are made without
having to restart QGIS.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 1 . 2 . D E BU G G I N G 157
import glob
import os
import subprocess
ui_files = glob.glob('*.ui')
for ui in ui_files:
(name, ext) = os.path.splitext(ui)
print "pyuic4 -o {}.py {}".format(name, ui)
subprocess.call(["pyuic4.bat", "-o", "{}.py".format(name), ui])
rc_files = glob.glob('*.qrc')
for rc in rc_files:
(name, ext) = os.path.splitext(rc)
print "pyrcc4.exe -o {}_rc.py {}".format(name, rc)
subprocess.call(["pyrcc4.exe", "-o", "{}_rc.py".format(name), rc])
11.2 Debugging
When log messages and print statements aren’t enough to troubleshoot your
PyQGIS code, you may have to resort to interactive debugging. There are a
several options for debugging your code:
Using pdb
When you want to cause your code to stop and drop into the debugger, place
these lines at the desired breakpoint:
pyqtRemoveInputHook()
pdb.set_trace()
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
158 C H A P T E R 1 1 . C R E AT I N G A D E V E L O P M E N T WO R K F L OW
The key to using pdb is to start QGIS from a terminal. On Linux, you
simply run qgis from the command line.
The Windows version of QGIS doesn’t allow us to start it in a way that pdb
can attach to the process—you’ll have to use one of the other methods listed
below.
Once pdb is active in your terminal, you can use commands to list the
source, set breakpoints, view the contents of variables, and step through
your code. Typing help gives you a summary of the available commands,
many of which have one letter abbreviations:
(Pdb) help
Undocumented commands:
======================
retval rv
For more information and details on using pdb, see the documentation at:
http://loc8.cc/ppg/pdb
There are a number of IDEs that support remote debugging, meaning they
can attach to a running QGIS project and provide debug capability. Here
are two that are known to work with QGIS:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 1 . 2 . D E BU G G I N G 159
• PyDev (Eclipse)
• PyCharm (commercial)
We talked about installing PyDev in Section 3.2, Using an IDE, on page 37.
For a complete description on configuring and using PyDev to debug a
QGIS plugin, see:
http://linfiniti.com/2011/12/remote-debugging-qgis-python-plugins-with-pydev/
This blog post contains a complete example starting with creating a simple
plugin to creating the PyDev project and using the remote debugger.
If you don’t use an IDE or yours doesn’t support remote debugging you
can use Winpdb.29 Despite its name, Winpdb is a cross platform debugging
tool that works on Linux, Mac OS X, and Windows. It does require the
installation of wxPython which is available for each of the three operating
systems.30 29
http://winpdb.org
30
http://www.wxpython.org
As with pdb, we need to add a few lines of code to enable debugging:
import rpdb2
rpdb2.start_embedded_debugger(password)
We can use the tools to step through the code, examine variables, and set
additional breakpoints.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
160 C H A P T E R 1 1 . C R E AT I N G A D E V E L O P M E N T WO R K F L OW
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
12
Writing a Standalone Application
Using Qt and the QGIS API, you can create your own standalone GIS ap-
plication that contains only the functionality you need. Some reasons to do
this include:
• You need a custom, streamlined data collection application for field use
• You want to restrict functionality to provide a simple or secure applica-
tion
• You want to include QGIS functionality in a larger application that isn’t
necessarily GIS-centric
1. Use Qt Designer to layout the main window and other GUI elements
We’ll use the second method, as it will help you understand more about
what’s going on “under the hood”.
Just as you can use the interactive shell to experiment with Python basics,
you can also use it to bring up a simple Qt application. Here is a short
example that we can try from the Python shell—we’ll use this as a starting
point on which to build our application.
If you enter these statements (or run them from a script) from the Python
shell, you’ll get a simple application shown in Figure 12.1, on the next page.
Let’s take a quick look at what’s going on in the code. In line 1 we import
the PyQt4.QtGui module since it contains the classes we need to create our
little application. Here’s an annotated list of lines 3-7:
Line 6: Set the frame as the central widget of our main window (every main
window has one)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 2 . C R E AT I N G A M I N I M A L P Y Q G I S A P P L I C AT I O N 163
Line 7: Create a grid layout that has the frame as its parent
These statements setup the basics of our application and window. The grid
layout will allow us to have PyQt dynamically resize all our child widgets
when the main window is resized.
Now we are ready to add the core of our application; a text edit control in
line 9. Notice we create it without specifying a parent widget (if it had a par-
ent, it would have been specified as an argument to QtGui.QTextEdit()).
In lines 10-13 we add some text to our editor, just so we can make sure it’s
working when we run the app. In line 14 we add our text editor to the grid
layout widget, which will handle the dynamic layout for us.
The last two things are to show the main window in line 15 and then add
line 17 in case we want to run this code as a script from the command line.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
164 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
editor control:
This gives us the little application shown in Figure 12.2, on the facing page.
It isn’t much to look at—no toolbars, menus, map controls, or legend, just a
map canvas with a single layer. Let’s take a deeper look at the code required
to get the app up and running.
We started out with the basic text editor app and substituted it with a QgsMap-
Canvas. To get that to work, we have to do a bit of setup first.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 2 . C R E AT I N G A M I N I M A L P Y Q G I S A P P L I C AT I O N 165
Lines 9 through 12 setup the main window and grid layout and are the same
as in our simple PyQt app.
We want the canvas to fill the application window and do that by adding it
to the grid layout in line 15.
By default the map canvas has a black background. Line 16 uses the set-
CanvasColor method to set it to white.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
166 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
To make sure our added layer is visible, we zoom the canvas to the full
extent of the shapefile in line 24.
The last thing to do is show the main window in line 26. Since we want
to run the code as a script instead of individually entering statements in
the Python shell, we need the app.exec_() call in line 29, otherwise our
application will come up and immediately exit.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 3 . C R E AT I N G O U R OW N MA I N W I N D OW C L A S S 167
30 QgsMapLayerRegistry.instance().addMapLayer(layer)
31 canvas_layer = QgsMapCanvasLayer(layer)
32 self.map_canvas.setLayerSet([canvas_layer])
The OurMainWindow class contains much of the code from our original
script, but we’ve made a start on making it more modular and easier to
understand.
The first thing you should notice is that our class is a subclass of QMain-
Window (line 9). This allows us to extend QMainWindow and add additional
functionality to it.
The __init__ method sets up our GUI, adds a shapefile, and zooms to full
extent. Ultimately we would want to add methods to our class to choose the
shapefile we want to load.
The setupGui method creates the GUI and adds the map canvas to our main
window.
Lastly, the add_ogr_layer method takes the path to our shapefile and adds
it to the map, much the way our earlier script did.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
168 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
18 mw = None
19 # clean up QGIS
20 QgsApplication.exitQgis()
This app needs a lot more work to make it useful. Let’s add some map tools
to allow us to control the map view.
There is more than one way to add a menu or tool in Qt, however the most
flexible way is to use a QAction. This allows us to add the action to both
the menu and the toolbar, rather than creating code to do both. Here is our
action:
self.zoomin_action = QAction(
QIcon(":/ourapp/zoomin_icon"),
"Zoom In",
self)
This creates the action, but it doesn’t do anything yet because it isn’t con-
nected to a method to actually zoom the canvas. Here is the new version of
ourmainwindow.py:
ourmainwindow_2.py
1 import os
2
3 from PyQt4.QtGui import *
4
5 from qgis.gui import *
6 from qgis.core import *
7
8 import resources
9
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 4 . A D D I N G MA P TO O L S TO T H E A P P L I CAT I O N 169
10
11 class OurMainWindow(QMainWindow):
12 def __init__(self):
13 QMainWindow.__init__(self)
14
15 self.setupGui()
16
17 self.add_ogr_layer('/data/alaska.shp')
18 self.map_canvas.zoomToFullExtent()
19
20 def setupGui(self):
21 frame = QFrame(self)
22 self.setCentralWidget(frame)
23 self.grid_layout = QGridLayout(frame)
24
25 self.map_canvas = QgsMapCanvas()
26 self.map_canvas.setCanvasColor(QColor(255, 255, 255))
27 self.grid_layout.addWidget(self.map_canvas)
28
29 # setup action(s)
30 self.zoomin_action = QAction(
31 QIcon(":/ourapp/zoomin_icon"),
32 "Zoom In",
33 self)
34 # create toolbar
35 self.toolbar = self.addToolBar("Map Tools")
36 self.toolbar.addAction(self.zoomin_action)
37
38 # connect the tool(s)
39 self.zoomin_action.triggered.connect(self.zoom_in)
40
41 # create the map tool(s)
42 self.tool_zoomin = QgsMapToolZoom(self.map_canvas, False)
43
44 def add_ogr_layer(self, path):
45 (name, ext) = os.path.basename(path).split('.')
46 layer = QgsVectorLayer(path, name, 'ogr')
47 QgsMapLayerRegistry.instance().addMapLayer(layer)
48 canvas_layer = QgsMapCanvasLayer(layer)
49 self.map_canvas.setLayerSet([canvas_layer])
50
51 def zoom_in(self):
52 self.map_canvas.setMapTool(self.tool_zoomin)
Let’s look at the changes needed to get our Zoom In tool visible and work-
ing. In line 8 we import our resources file. This file contains the definition
of resources needed in our app, in this case just an icon for the Zoom In tool.
The resources.py file is created by compiling resources.qrc using the
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
170 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
pyrcc4 tool, just as we did in Section 10.4, Modifying the Resources File,
on page 139.
We also need a graphics file for the toolbar icon—we created a resources
We used mActionZoomIn.png found subdirectory and copied mActionZoomIn.png into it.
in the QGIS Documentation source:
https://github.com/qgis/QGIS- Compiling the resources file gives us resources.py:
Documentation/tree/master/resources/
en/docs/common. pyrcc4 -o resources.py -o resources.qrc
Lines 29 through 42 create the action and setup the zoom in tool. In line 31
we reference the icon specified in our resource file by using its alias.
In addition to the action, we need to create the QGIS map tool. This is done
in line 42, where we create a QgsMapToolZoom object, setting its parent
to the map canvas and specifying False as the second argument to make it
zoom in (setting to True would make a zoom out tool).
The last thing we need is the zoom_in method which simply sets the current
map canvas tool to our zoom in tool (lines 51 and 52).
With that, we can run the app and, as we see in Figure 12.3, on the next page,
we now have a toolbar with our zoom in tool and we can use it to manipulate
the display. Note the cursor has changed to indicate the zoom in tool is
active. You might also have noticed that each time we run the application,
the fill color of our Alaska shapefile is different. This is because QGIS
supplies a random color when a layer is loaded. It will take some additional
work to select a color when the layer is loaded.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 2 . 4 . A D D I N G MA P TO O L S TO T H E A P P L I CAT I O N 171
• Map tools to zoom out, pan, zoom to extent, and zoom full
• Set layer colors
• Select the shapefile to load by providing a file selection dialog box
Packaging your standalone application can be a challenge, but there are some
utilities that can help:
• Linux: Freeze http://wiki.python.org/moin/Freeze
• Mac: py2app https://pypi.python.org/pypi/py2app/
• Windows: py2exe http://www.py2exe.org/
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
172 C H A P T E R 1 2 . W R I T I N G A S TA N DA L O N E A P P L I C AT I O N
12.5 Exercises
1. Add a title to the main window of the app
2. Add some additional map tools to the app: zoom out, pan, and zoom
full
12.6 Summary
That’s it---you’ve completed your first excursion into the world of PyQGIS
programming! For more information, be sure to check out the following
resources:
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
13
Answers to Exercises
1. addProject
2. addRasterLayer
3. iface.addVectorLayer(’/path/to/world_borders.shp’, ’world_borders’,
’ogr’)
• Plugins->ScriptRunner->ScriptRunner
• Plugins->Plugin Builder...->Plugin Builder
3. Use the mouse to hover over each icon in the Plugins toolbar to
locate each plugin
d. Click OK
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 3 . 5 . E X E R C I S E S : RU N N I N G S C R I P T S 175
2. Change the color to green with 50% transparency. You need to import
QColor in order for this work:
from PyQt4.QtGui import QColor
renderer = wb.rendererV2()
symbol = renderer.symbol()
symbol.setColor(QColor(0, 255, 0))
symbol.setAlpha(0.5)
Use symbol.setColor to set the new color and if transparency is not None,
use symbol.setAlpha to set the transparency.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
176 C H A P T E R 1 3 . A N S W E R S TO E X E R C I S E S
2. Use the URI method found in Section 8.1, Memory Layers, on page 86.
4. Use a QInputDialog.getText to get the new name, then get the object
(QgsFeature) for the selected feature. Modify the name and update
the attribute table using the data provider method. See Section 8.6,
Editing Attributes, on page 101 for hints.
2. Using Qt Designer remove the Line Edit widget and replace it with
a Label widget. Modify the code to reference the new widget in the
display_point function. Be sure to compile your UI changes using
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 3 . 9 . E X E R C I S E S : W R I T I N G A S TA N DA L O N E A P P L I C AT I O N 177
pyuic4.
3. Use the setSelection method of QLabel to select the text when the
new QToolButton is clicked, then copy it to the clipboard using QClip-
board. You will need to connect the triggered signal of the button to
a new method that selects the text and copies it to the clipboard. Test
your work by pasting into your text editor.
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
14
Appendix A: Installing QGIS
The installation instructions in this appendix are geared towards getting you
a functional install to support programming with PyQGIS.
3. All the defaults for the install are fine, with the exception of installa-
tion directory. Be sure to choose C:\qgis_2.0 or another directory
name that does not contain spaces.
set PATH=%PATH%;C:\qgis_2.0\apps\qgis\bin
set PYTHONPATH=C:\qgis_2.0\apps\qgis\python
To use Python from the command line, run the OSGeo4W shell that was
installed with QGIS and then execute the pyqgis.cmd script. You now
have full access to the PyQGIS API from the interactive Python shell.
You might want to put pyqgis.cmd in your path to make it easy to run
from any directory.
To install QGIS, first download and install the GDAL Complete framework
package, available from the same page. Then install the appropriate QGIS
package.
In order to access the QGIS libraries from Python (outside of QGIS), you’ll
need to add the following to your environment:
• QGISBASE=/Applications/QGIS.app/Contents
• export DYLD_LIBRARY_PATH=$QGISBASE/MacOS/lib
• export PYTHONPATH=$QGISBASE/Resources/python
You can do this from a shell script that you source each time you want to use
the libraries, or add them permanently to your $HOME/.bash_profile. Be
sure to set QGISBASE to the location of you QGIS application (by default
/Applications/QGIS.app).
• Debian
• Fedora
• RHEL / CentOS / Scientific Linux
• openSUSE
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 4 . 4 . BU I L D I N G Q G I S 181
• Mandriva
• Ubuntu
• Slackware
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
15
Appendix B: Code Listings
You can download all the code listed in this book at http://locatepress.
com/ppg/data_code.
15.1 wrapper.py
24 else:
25 return None
26
27
28 def addOgrLayer(layerpath, name=None):
29 """ Add an OGR layer and return a reference to it.
30 If name is not passed, the filename will be used
31 in the legend.
32
33 User should check to see if layer is valid before
34 using it."""
35 if not name:
36 (path, filename) = os.path.split(layerpath)
37 name = filename
38
39 lyr = QgsVectorLayer(layerpath, name, 'ogr')
40 return QgsMapLayerRegistry.instance().addMapLayer(lyr)
41
42
43 def addGdalLayer(layerpath, name=None):
44 """Add a GDAL layer and return a reference to it"""
45 if not name:
46 (path, filename) = os.path.split(layerpath)
47 name = filename
48
49 lyr = QgsRasterLayer(layerpath, name)
50 return QgsMapLayerRegistry.instance().addMapLayer(lyr)
51
52
53 def removeLayer(layer):
54 QgsMapLayerRegistry.instance().removeMapLayer(layer.id())
55
56
57 def createRGBA(color):
58 (red, green, blue, alpha) = color.split(',')
59 return QColor.fromRgb(int(red), int(green), int(blue), int(alpha))
60
61
62 def changeColor(layer, color):
63 """ Change the color of a layer using
64 Qt named colors, RGBA, or hex notation."""
65 if ',' in color:
66 # assume rgba color
67 color = createRGBA(color)
68 else:
69 color = QColor(color)
70
71 renderer = layer.rendererV2()
72 symb = renderer.symbol()
73 symb.setColor(color)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
15.2. WHEREAMI PLUGIN 185
74 layer.setCacheImage(None)
75 iface.mapCanvas().refresh()
76 iface.legendInterface().refreshLayerSymbology(layer)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
186 CHAPTER 15. APPENDIX B: CODE LISTINGS
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
15.2. WHEREAMI PLUGIN 187
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
188 CHAPTER 15. APPENDIX B: CODE LISTINGS
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
1 5 . 3 . S TA N DA L O N E A P P L I C AT I O N 189
5
6 app = QApplication([])
7 # set up QGIS
8 QgsApplication.setPrefixPath('/dev1/apps/qgis', True)
9 QgsApplication.initQgis()
10
11 # set the main window and show it
12 mw = OurMainWindow()
13 mw.show()
14
15 app.exec_()
16
17 # "delete" our main window
18 mw = None
19 # clean up QGIS
20 QgsApplication.exitQgis()
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
190 CHAPTER 15. APPENDIX B: CODE LISTINGS
32 "Zoom In",
33 self)
34 # create toolbar
35 self.toolbar = self.addToolBar("Map Tools")
36 self.toolbar.addAction(self.zoomin_action)
37
38 # connect the tool(s)
39 self.zoomin_action.triggered.connect(self.zoom_in)
40
41 # create the map tool(s)
42 self.tool_zoomin = QgsMapToolZoom(self.map_canvas, False)
43
44 def add_ogr_layer(self, path):
45 (name, ext) = os.path.basename(path).split('.')
46 layer = QgsVectorLayer(path, name, 'ogr')
47 QgsMapLayerRegistry.instance().addMapLayer(layer)
48 canvas_layer = QgsMapCanvasLayer(layer)
49 self.map_canvas.setLayerSet([canvas_layer])
50
51 def zoom_in(self):
52 self.map_canvas.setMapTool(self.tool_zoomin)
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
16
Appendix C: Porting Scripts to 2.0
Porting scripts from 1.x to 2.0 can be a matter of trial and error. The QGIS
wiki has a list of the changes from 1.8 to 2.033 , as well as a summary of
changes in the API.34 33
http://loc8.cc/ppg/api_
changes
[general]
name=Display coordinates of a map click
qgisMinimumVersion=2.0
description=Display coordinates of a map click
version=0.1
author=gsherman
email=gsherman@geoapt.com
192 C H A P T E R 1 6 . A P P E N D I X C : P O RT I N G S C R I P T S TO 2 . 0
# Optional items:
homepage=
tracker=
repository=
icon=whereami_icon.png
# experimental flag
experimental=False
# deprecated flag (applies to the whole plugin, not just a single version
deprecated=False
To ensure your plugin can be loaded, the mandatory items must be present.
If you specify a qgisMinimumVersion less than 2.0, it won’t show up in the
Plugin Manager.
With the SIP upgrade, some types were removed and substituted with native
Python types. The QVariant and QString classes are no longer part of the
QGIS 2.0 API.
These and other changes are detailed in the Python Plugin API Changes
from 1.8 to 2.0 document.36 It provides examples and of changes required
36
http://loc8.cc/ppg/api_ to migrate a plugin from 1.8 to 2.0.
changes
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
17
Index
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
195
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
196 CHAPTER 17. INDEX
T H I S B O O K I S C O P Y R I G H T E D — D O N OT D I S T R I B U T E
Books from Locate Press
The Geospatial Desktop provides a foundational level of knowledge for under-
standing GIS and the open source desktop mapping applications that are available for
use, for free, today.
Learn about vector and raster data, how
to convert data, interacting with spatial
databases, creating new map data, geopro-
cessing, scripting, and more.
Special sections include focused learning on
the Quantum GIS and GRASS GIS software
platforms as well as an introduction to other
packages.
The Geospatial Desktop is written by the
founder of the Quantum GIS project, so you
can rest assured that you will be led by one of the most knowledgeable authors on
the subject.
The Quantum GIS Training Manual Get the jump-start you need
to learn this incredibly popular free desktop mapping and GIS toolset.
Comprehensive and structured, your intro-
duction begins with a quick download of ex-
ample data, making it easy for you to work
your way through the concepts and practical
exercises, complete with answers and exam-
ples.
Ideal for classroom instruction and self-
guided learning, included are all the materi-
als needed to run a five day course on Quan-
tum GIS, PostgreSQL and PostGIS. Content
is structured for novice, intermediate and advanced users alike. Seasoned Quantum
GIS users will also find tips and new techniques to apply to every mapping project.
Windows, Mac OS X, or Linux? It’s your choice, this book works for all.
Geospatial Power Tools Everyone loves power tools. The GDAL and OGR
utilities are the power tools of the GIS world, and best of all, they’re free.
The utilities include tools for examining, con-
verting, transforming, building and analysing
data. This book is a collection of the
GDAL and OGR documentation, but also in-
cludes substantial new content designed to
help guide you in using the utilities to solve
your current data problems.
Inside you’ll find a quick reference for look-
ing up the right syntax and example usage
quickly. The book is divided into three parts:
• Part I - Workflows and examples
• Part II - GDAL raster utilities
• Part III - OGR vector utilities
Once you get a taste of the power the GDAL/OGR suite provides, you’ll wonder
how you ever got along without them. This book will get you on the fast track to
becoming more efficient in your GIS data processing efforts.