Animated QGIS map canvas item


Have you ever wanted to animate a QGIS map canvas item. No? Well soon you will.

First we need to create a custom QgsMapCanvasItem

from PyQt4.QtCore import QPointF, QRectF, QTimer, QObject, pyqtProperty, QPropertyAnimation, Qt
from PyQt4.QtGui import QPainter, QBrush, QColor
from qgis.gui import QgsMapCanvasItem
from qgis.core import QgsPoint

class PingLocationMarker(QgsMapCanvasItem):
    """
    Position marker for the current location in the viewer.
    """
    class AniObject(QObject):
        def __init__(self):
            super(PingLocationMarker.AniObject, self).__init_<a href="https://woostuff.files.wordpress.com/2014/10/marker.gif"><img src="https://woostuff.files.wordpress.com/2014/10/marker.gif?w=300" alt="marker" width="300" height="193" class="alignnone size-medium wp-image-61493" /></a>_()
            self._size = 0
            self.startsize = 0
            self.maxsize = 32

        @pyqtProperty(int)
        def size(self):
            return self._size

        @size.setter
        def size(self, value):
            self._size = value

    def __init__(self, canvas):
        self.canvas = canvas
        self.map_pos = QgsPoint(0.0, 0.0)
        self.aniobject = PingLocationMarker.AniObject()
        QgsMapCanvasItem.__init__(self, canvas)
        self.anim = QPropertyAnimation(self.aniobject, "size")
        self.anim.setDuration(1000)
        self.anim.setStartValue(self.aniobject.startsize)
        self.anim.setEndValue(self.aniobject.maxsize)
        self.anim.setLoopCount(-1)
        self.anim.valueChanged.connect(self.value_changed)
        self.anim.start()

    @property
    def size(self):
        return self.aniobject.size

    @property
    def halfsize(self):
        return self.aniobject.maxsize / 2.0

    @property
    def maxsize(self):
        return self.aniobject.maxsize

    def value_changed(self, value):
        self.update()

    def paint(self, painter, xxx, xxx2):
        self.setCenter(self.map_pos)

        rect = QRectF(0 - self.halfsize, 0 - self.halfsize, self.size, self.size)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setBrush(Qt.green)
        painter.setPen(Qt.green)
        painter.drawEllipse(QPointF(0,0), self.size, self.size)

    def boundingRect(self):
        return QRectF(-self.halfsize * 2.0, -self.halfsize * 2.0, 2.0 * self.maxsize, 2.0 * self.maxsize)

    def setCenter(self, map_pos):
        self.map_pos = map_pos
        self.setPos(self.toCanvasCoordinates(self.map_pos))

    def updatePosition(self):
        self.setCenter(self.map_pos)

marker = PingLocationMarker(iface.mapCanvas())

and this is the result

marker

wweeeee animated canvas item.

Run the above code in a QGIS Python console editor window and you should get the same effect.

So what is this magic? Well it turns out to be pretty easy all thanks to the handy class QPropertyAnimation. QPropertyAnimation takes a QObject and sets a property value until the end value is hit over the duration, the cool thing with this class is that it can take any QVariant type, which is pretty much anything, and it will go from start value to end value. You can also use other easing curves to change how the values change over time.

Super nifty!

The main important part of this is:

self.anim = QPropertyAnimation(self.aniobject, "size")
self.anim.setDuration(1000)
self.anim.setStartValue(self.aniobject.startsize)
self.anim.setEndValue(self.aniobject.maxsize)
self.anim.setLoopCount(-1)
self.anim.valueChanged.connect(self.value_changed)
self.anim.start()

which calls the value_changed and self.update() methods, when update is called paint will be called and we grab the current animation value. Note: -1 loop count means run forever.

self.aniobject is a custom QObject to hold our current animation value. QgsMapCanvasItem is not a QObject so we have to make another object to hold that value for us. I tried double inheritance here and it didn’t like it so I went with a nested class which is nice anyway.

And that is all you need to make a animated QgsMapCanvasItem, remember that QPropertyAnimation can be used on any QObject so you could do some pretty cool stuff with this if you have the need.

Be interested to hear any ideas on if we can use this in QGIS.

Note: A canvas item like this isn’t part of any layer. Canvas items live on on the canvas itself, above or below the map image. The markers that you see when you enable editing on a layer are canvas items, as are the lines when drawing a measure line, even the image you see in the canvas is a canvas item, etc.

Thank you from Stacey and I.


Though the kindness of everyone we made it to over $2000 to donate two, not one like we had planned at the start, camera packs to hospitals that need them. One will be going to the new Gold Coast hospital where Ellie was born and the other to another hospital who needs it. Both cameras will have Ellie’s name on them in her memory. They will go a long way to help preserve the memories of those last minutes just like it did for us with Ellie.

The money is now with Heartfelt and hopefully the cameras will be done soon. I will update this post with photos once they are done.

It was really amazing to see the number of people who I have never meet in person from all over the internet throwing in what they could to help us reach the goal. We are extremely grateful for everything everyone put in.

A massive thanks to everyone who donated:

  • Lyn Noble
  • Kylie and Nathan
  • Luke Bassett
  • Joanne Smith
  • Darryl & Angela Browning
  • Digital Mapping Solutions
  • David Baxter
  • Carl Wezel
  • Grandad and Grandma Woodrow
  • Bill Williamson
  • Helen Gillman
  • Karlie Jones
  • Lisa Gill
  • Andrew & Peta
  • Sally Drews
  • Matt Travis
  • Mummy, Daddy, Harry & Little Sis..
  • Terry Stigers
  • James McKeown
  • Jill Pask
  • Kym Zevenbergen
  • Jessica Nayler
  • Amelia Woodrow
  • Judy Burt
  • Russell and Suzann Woodrow
  • Jenny & Mark Gill
  • Emeley Sands
  • Rebecca Penny
  • Larissa Collins
  • Ross McDonald
  • Shantelle Sweedman
  • Rebbecca Ben izzy Erica
  • Aidan Woodrow & Andrew Smith
  • simbamangu
  • Sarah Rayner
  • Sassá
  • Matt Travis
  • Marco Giana
  • Heikki Vesanto
  • Jorge Sanz
  • Pure K.
  • Toby Bellwood
  • Andy Tice
  • Ujaval Gandhi
  • Matt Robinson
  • Geraldine Hollyman
  • Anonymous
  • Teresa Baldwin
  • Alexandre Neto
  • Chelsea Fell
  • Stephane Bullier
  • Nathan Saylor
  • Adrien ANDRÉ
  • Steven Feldman
  • Anita Graser
  • Chris Scott
  • Vicky Gallardo
  • Anonymous
  • Anonymous
  • Stevie Little

I will also add a massive thank you to DMS who has been super supportive though this whole year since Elly died, and they know very well the effect it has had on Stace and I over the past year.

In a perfect world we would never had to run a fund raiser for this but I’m glad Heartfelt exist to help those of us in need at the time.

Thank you from Stacey and I.

QGIS atlas on non geometry tables


This is proof that no matter how close you are to a project you can still miss some really cool stuff that you never knew or considered was possible.

The problem to solve:

You have a CSV with a row of colours. Each row should be a new map and each column is the colour for that feature.

This is example of that kind of input

A       B
#93b2f3    #FF0000 
#dfbdbb    #FF0000
#f9d230    #FF0000

This questhion was asked on GIS.SE this morning. When I first saw it I had no idea it was even possible, I was thinking along the same lines as the person asking, that it would have to be done with Python. Not hard, but a lot harder then something built in and I put it in the too hard basket. I thought the atlas can almost do that, almost but not really.

Well almost was wrong. It can.

Note: You will need QGIS 2.5 (2.6 when released) for this to work

Lets make some cool maps! (and go to GIS.SE and upvote Nyalls answer)

First open your vector layer and the CSV. Don’t worry about style just yet, we will do it later.

Create a composer and add your map.

Here comes the first part of the trick.

Enable Atlas and set the coverage layer to the CSV layer. Wait? What? That doesn’t make any sense. If you think about it for a while it does. We need a map for each row (or “feature”) in the CSV and atlas does just that.

atlas_colours

How do we style the features? Well here is the other part of the trick. In 2.6 there is a magic expression function that returns a field value from another feature. And it’s as simple as attribute( $atlasfeature , 'A' ) – give me the attribute from the current atlas feature for field ‘A’. Simple.

First we categorize our features so we have a symbol for each feature. I’m using a sample layer I have but you can understand how this works. The first feature is A and the other is B, etc, etc

render

Now to use another awesome feature of QGIS. The data defined symbol properties (and labels too). Change each symbol and define the colour data defined property. Using attribute( $atlasfeature , 'A' ) for the first one and attribute( $atlasfeature , 'B' ) for the second.

atlas_feature

That is it. Now jump back over to your composer and enable Atlas preview.

atlas1

atlas2

Bam! Magic! How awesome is that!

Now my other thought was. “Ok cool, but the legend won’t update”. I should learn by now not to assume anything. The legend will also update based on the colours from the feature.

atlas1_legend

atlas2_legend

How far can we take this. What if you need the label to match the colour. Simple just make the label text look like this:

<h1 style='color:[% "A" %]'>This is the colour of A</h1>

atlas1_label

Heaps of credit to Nyall and the others who have added all this great stuff to the composer, atlas, and the data defined properties. It’s not something that you will do every day but it’s great to see the flexibility of QGIS in these situations.

You can even make the background colour of the page match the atlas feature

atlas1_back

but don’t do that because people might think you are mad ;)

Exporting QGIS symbols as images


Ever wanted to export your QGIS symbols as images? Yes. Well here is some Python code that will let you do just that:

from PyQt4.QtCore import QSize
from PyQt4.QtGui import QImage, QPainter

style = QgsStyleV2.defaultStyle()
names = style.symbolNames()
size = QSize(64, 64)

for name in names:
    symbol = style.symbol(name)
    if not symbol.type() == QgsSymbolV2.Marker:
        continue

    image = QImage(size, QImage.Format_ARGB32_Premultiplied)
    image.fill(0) 
    painter = QPainter(image)
    symbol.drawPreviewIcon(painter, size)
    painter.end()
    image.save(r"C:temp{}.png".format(name), "PNG")

Or in 2.6 it’s even easier:

from PyQt4.QtCore import QSize
from PyQt4.QtGui import QImage, QPainter

style = QgsStyleV2.defaultStyle()
names = style.symbolNames()
size = QSize(64, 64)

for name in names:
    symbol = style.symbol(name)
    if not symbol.type() == QgsSymbolV2.Marker:
        continue

    image = symbol.asImage(size)
    image.save(r"C:temp{}.png".format(name), "PNG")

Bam!

symbols

Why? Because we can.

Fundraising for Eloise and Heartfelt


The death of our daughter was one of the hardest things my wife and I have ever had to deal with. It’s not something that I wish anyone ever have to experience and feel really sorry for those who have had to do it many times. There is something really raw about losing your own flesh and blood. It cuts deep, really really deep. There are really no words to describe the emptiness that you feel, or the feelings that follow after the event. Even with all the pain of loosing a child there is a great Australian service that helps to capture some of the final monents. The service is called Heartfelt and we used them for Ellie.

Heartfelt is a great free service that provides a photo session, including editing and prints (hard and digital) after, in the last and final days. This is the quote from their site:

Heartfelt is a volunteer organisation of professional photographers from all over Australia dedicated to giving the gift of photographic memories to families that have experienced stillbirths, premature births, or have children with serious and terminal illnesses.

Heartfelt is dedicated to providing this gift to families in a caring, compassionate manner.

All services are provided free of charge

Pretty impressive stuff. The last thing you want to have to do in a time like that is shell out for photos when you have other pressing issues.

As Ellie’s 1st Birthday is coming in up October Stace and I would love to raise enough money to donate a camera pack to a hospital though Heartfelt in Elly’s name. We have created a fundraiser page in her name at: http://www.mycause.com.au/page/79669/eloises1stbirthdayheartfelt in order collect dontations for anyone who would like to help.

Camera packs can be donated to a hospital to allow staff at the hospital to capture photos if Heartfelt can’t make it. The bonus is that Heartfelt will still edit and print the photos. How bloody awesome is that! More info on the camera packs is at: http://www.mycause.com.au/page/79669/eloises1stbirthdayheartfelt

We would be greatful for any donations, big or small, so we can donate a camera pack in Elly’s name.

We love and miss you a lot Eloise.

QGIS Needs You! Help make QGIS 2.4 better


QGIS is now in feature freeze for the 2.4 release, that means no more features are going in and we need to focus on fixing any outstanding issues that are still hanging around before the release. 2.4 is going to be a good release, adding cool things like: multithreaded rendering; legend code refactor; colour blind previews; and a whole heaps of other cool stuff. We need your help finding and squashing bugs. This is where you come in.

Finding bugs

Grab the RC builds of QGIS from the downloads page. If you are on Windows I would recommand grabbing the OSGeo4W installer and installing the package called qgis-dev using the Advacned option. A new build will show up nighly and you can test the lastest version.

If you find a bug you need to log it at hub.qgis.org, if you don’t we can’t fix it. Don’t post a tweet about it and hope that we pick it up because we may not, this happened recently and the person didn’t file tickets when I asked them too and now it’s forgotten.

We track everything at hub.qgis.org. We close tickets as we fix them so keep an eye out for ones that you open. Remember to always add as much information as possible, and answer questions if asked. We are aware that everyone is busy, as are we, however if you don’t responed it can be hard to track down issues at times. It can take a bit of time to get used to what is a good or a bad ticket but it doesn’t take long. Next time you see a bug file it at hub.qgis.org.

Squashing bugs

This is where the help really matters and is the best thing you can do for the project. If you’re a developer and keen to try your hand at some bugfixing you can find the most important ones here.

Not a developer?

The next best thing you can do is fund some bug fixing. There are many ways to do this and this is the most effective way to get stuff done.

Your main options are:

  • Donation to the QGIS fund – we use some of this money to pay for bug fixing.
  • Hire a developer directly. This is a good way to go as it is focused development. You can find some of the devs here
  • Rob a bank and send the money to us – No!11! Don’t do that.
  • Encourage your company – who maybe is now saving a lot of money – to sponser or hire a developer.
  • Run a user group and charge a small fee to donate to the project – minus costs of course.

It’s not just code.

There is more to QGIS then code and some application at the other end. With each release comes other non developer work.

These things include:

  • Updating the manual
  • Updating translations
  • Helping with PR stuff like posters
  • Ticket clean up

If you’re not in a position to help in the other areas of the project these things need love to so don’t forget you can help here.

We love that QGIS is free, that it opens GIS to a whole range of people who would never have been able to use it. It’s a great feeling. It’s also a great feeling when others get invovled and help us along to make it better for everyone.

qgis2img – A QGIS render benchmarking tool and image renderer


qgis2img is a new tool that I created, in a bit of friendly competition with the boss, which I lost but we will not speak of that again, for benchmarking QGIS layer rendering. The goal is simple. Take a project file(s), or QLR file(s), render the output, time the results, and dump a summary. Simples. The tool does 3 passes by default to get the average but can do more. It’s nothing fancy. Written in Python so it can be evolved quickly.

qgis2img will render each image by itself to give single timings then it will render the whole project as you see in QGIS.

It uses QGIS 2.4 (qgis-dev) in order to use the new rendering methods. I don’t have any plans to port it to work with QGIS 2.2, however feel free to send a pull request.

The usage is pretty simple:

usage: qgis2img [-h] [--size SIZE] [--passes PASSES] [--types TYPES] file

Benchmark QGIS project file and layer loading times

positional arguments:
  file             Project file to load into QGIS

optional arguments:
  -h, --help       show this help message and exit
  --size SIZE      Image output size
  --passes PASSES  Number of render passes per layer
  --types TYPES    What to render. Options are layer|project, layer, or project.
                   layer|project will render all layers as the if the project
                   is open in QGIS.

with the results:

$ python.exe qgis2img parcels.qgs --passes 5
Project Loaded with: [u'PARCEL_region - Shp', u'PARCEL_region - Spatialite']
Rendering images with 5 passes
Layer: PARCEL_region - Shp      4.907 sec
Layer: PARCEL_region - Spatialite       3.66 sec
Layer: Project     5.3378 sec

Easy.

It will generate an image for each layer and the project:

qgis2img

You can find the project at https://github.com/DMS-Aus/qgis2img

Pull requests and ideas welcome

qgisbench

There is a tool called qgisbench in the QGIS source tree that does this kind of thing too, however:

  • It’s in C++
  • We don’t ship it
  • It’s in C++
  • <3 Python
  • These things are good examples for others
  • Using the Python API in this ways lets me see gaps