- RFP: Agriculture Inventory & Land Use Mapping Plugin for QGIS
- Announcing the release of QGIS 1.6 'Copiapó'
- Announcing the release of QGIS 1.5
- Quantum GIS on steroids
- Annotation tools
- Announcing the release of QGIS 1.4.0 'Enceladus'
- Carson Farmer's report back on the Vienna Hackfest
- Vienna Hackfest 2009 Report Back
- Introducing the QGIS Hackfest (Vienna 2009) crew
- Announcing the release of QGIS 1.3.0 'Mimas'
Writing a Renderer in Python
For my project I want to display point data in pretty colours. I have survey data for villages that record the number of cases of disease and the village population, and I want to colour according to the percentage infected. Red for high, green for low.
Now, I could compute ncases/population, add that to the attribute table of my shapefile, and use the Continuous Color Renderer, but I don't want to impose anything else on my data files. So I figured I'd write a renderer that took two attributes and coloured accordingly.
In Python.
So I start subclassing QgsRenderer. When a layer wants to be drawn, the renderFeature method of its associated renderer is called for each feature. This method gets given the feature, a Qt painter, a Qt image object, and sets the brush and pen and image to the glyph or colour style to draw the feature in.
This was the big problem. My python renderFeature method gets passed a QImage() object. Just like that. A null QImage object. The method has to modify that object in place. I thought all I needed to do was to set the image to the right size and draw an ellipse of the right colour.
But QImage has no .setWidth(), .setHeight(), or .setSize() methods. What the? It seemed there was no way of starting with a null QImage, and turning it into a pixellated work of art.
Martin suggested some SIP annotations to the renderFeature method, but these only affect what a wrapped C++ method returns to Python - what I've got is a method of a Python class which is being called by a C++ function via some SIP method despatch magic. There may be some clever way of making this work with SIP but I gave up and Martin is a busy man.
For a while I turned to the dark side, and considered writing it as a C++ plugin. The trouble there (beyond getting caught by the subtle difference between QgisPlugin and QgsRendererPlugin) was then I would have to wrap the renderer with SIP so I could call it from Python, and even then I wasn't sure how the renderer would be exposed to Python anyway. The only way to get my percentage renderer working seemed to be for it to be coded into QGis at the same level as the other renderers. Sure, it would be a nice extra, but the developers would probably expect me to write a gui for it and make the code up to the same high standard that the rest of Qgis is written in.
I had another go at the python. I looked at the QImage documentation in the Qt assistant. Then I spotted that you could take an existing QImage and use the .load() method to set it from arbitrary data. Here was a way to resize the img parameter passed to my renderFeature!
So my code calls QgsMarkerCatalogue.instance().imageMarker ("hard:circle",10,pen, brush) to get a new QImage, and then uses QByteArray and QBuffer techniques so that I can do img.load(...). And it works.
If anyone has an easier way....
- spacedman's blog
- Login to post comments
- 8688 reads

Use the qimage ctor?
Hi
I dont know python but in c++ you can just specify the image size when you constuct your qimage:
QImage ( int width, int height, Format format )
Cant you just do the same in python?
Regards
Tim (timlinux)
You can but...
but...
The renderer is passed an empty, null, 0x0 QImage in its args, and is expected to do something with it - its pass-by-reference. In C++, the QImage '=' assignment operator is overloaded as a copy operator, so that when the C++ renderFeature code does 'img = QMarkerCatalogue.blah...()' the passed-in QImage object is changed.
Now in Python code the assignment operator creates a new object, and the overloaded C++ assignment/copy operator for QImage isn't exposed in the PyQt4 API. Since the only way to modify the passed-in QImage object in python is via method calls, like img.setSomethingHere(value), rather than assignments, I was stuck, because there's no QImage.setSize(s) method, and no other obvious way to change the size of the passed-in null QImage() object. So I had to use a non-obvious way!
Maybe the Trolls will add this in a later release....