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.

Leave a comment