In memory of Tim York


The other weekend the world lost a great guy, someone so nice that you struggle to find anything bad to say about him at all. Tim was only 27 but died tragically on the weekend, to the complete shock of everyone that knew him. I don’t think anyone really knew what to feel at that point, and even now it is still really hard to take it.

Tim was very well known in the GIS space here in Australia, and extremely well respected by everyone that knew him. I attended Locate15, where I was going catch up for a beer and chat with him, and almost everyone I talked to knew who he was and how great he really was. I would not doubt at all he could have become CEO or leader at a large company, no doubt at all.

It was mentioned at the funeral that we should try to think of how Tim made our lives better. For that I can fully thank Tim for my job at DMS. It was at a SSSI event after talking with my now manager about if they would like me at DMS that I had caught up with Tim and he encouraged me to move into the private sector. For that I thank him a lot.

I have started to read “How To Win Friends & Influence People” by Dale Carnegie, because one can always do with more skills in life, and some important points from the book:

  • Become genuinely interested in other people.
  • Smile.
  • Remember a person’s name.
  • Be a good listener. Encourage others to talk about themselves.
  • Talk in terms of the other person’s interests

This. All of this is what Tim did. This is the reason it always felt great to talk to him and be around him, even when I only saw him a few times.

I don’t know if Tim’s life was better for knowing me, given his large group of friends and great partner I suspect not, but I know sure as shit that mine was better for having known¬†him.

319187_10150351976087518_3388507_n

New addition


Stace and I would like to welcome Matilda Anne Woodrow, born 5/1/15 at 10:36am.

tilly

She is a nice way to start 2015.

After the pretty crappy year that was 2014 it was finally nice to see Matilda in the flesh. The relief of seeing her alive and well is hard to put in words after losing Ellie in 2013.

People always ask about the lack of sleep, but it feels like we have more energy now then we did before. The emotional and physical toll that the pregancy took on our family had pretty much run us into the ground and it felt like it was never ending.

The toll of the pregancy had lead me to massive lack of motivation towards QGIS and pretty much everything else in life, which isn’t healthy when you have a family to look after. Pro Tip: Get help if you find yourself in this spot, it be hard to recover if you go over the edge.

Anyway. Here is to a new year. A better year.

Function editor for QGIS expressions


A new feature for QGIS 2.8 is a function editor for expressions. Being able to define your own custom functions has be possible for a while now, over a year, I even have a plugin (Expressions+) that adds some extra ones, however it wasn’t easy for new users and required you to create files that lived on python path while also editing the startup.py file for QGIS so that functions got registed at start up. Way too much effort for my liking.

This feature is now exposed via the expression builder on the Function Editor tab. The function editor will create new Python files in qgis2\python\expressions and will auto load all functions defined when starting QGIS. I remember Sean Gillies saying that Python support in a field calculator should be a gateway to full Python programming, can’t get any more Python then full Python modules.

The best way to show this feature is a quick video so here it is

The play button in the editor will run the current editor file in the QGIS session and register any functions it finds, it will also save the file in the expressions folder.

You can make a new file by pressing the new file button which will add the basic function template, changing the name in the combobox and hitting save will save the file.

The function editor allows you have to define reuseable functions for your QGIS. If you want to share a function you simply share the .py file from the expressions folder and give it to another user. In future versions of QGIS we might have a way to download other users functions.

Auto expanding values

You can also use the args="auto" keyword in place of the number of args which will let QGIS work it out and expand the arguments for you.

Here is an example

@qgsfunction(args="auto", group='Custom')
def myfunc(value1, value2 feature, parent):
    pass

and can be used like this:

myfunc('test1', 'test2')

QGIS will workout how many arguments your function takes based on what you have defined. Note: The last arguments should always be feature, parent, these are not included in the count.

Raising errors

If you need to report a error from a function simply use the raise method like normal in QGIS and raise an exception.

@qgsfunction(args="auto", group='Custom')
def myfunc(value1, value2 feature, parent):
    raise Expection("Hahah Nope!")

The function wrapper will catch the exception and raise it though the expression engine.

A couple of things to note

  • New functions are only saved in the expressions folder and not in the project file. If you have a project that uses one of your custom functions you will need to also share the .py file in the expressions folder.
  • The editor doesn’t unregister any functions. If you define a function called test, hit play, change the name to test_2, hit play, both functions will exist until you reload QGIS.

Shortcuts to hide/show panels in QGIS


Just a quick one to show how to assign shortcuts to hide and show for panels in QGIS. Add this to your .qgis2\python\startup.py

from functools import partial
from qgis.utils import iface

from PyQt4.QtCore import *
from PyQt4.QtGui import *

mappings = {"Layers": Qt.ALT + Qt.Key_1,
            "Browser": Qt.ALT + Qt.Key_2,
            "PythonConsole": Qt.ALT + Qt.Key_3}
shortcuts = []

def activated(dock):
    dock = iface.mainWindow().findChild(QDockWidget, dock)
    visible = dock.isVisible()
    dock.setVisible(not visible)

def bind():
    for dock, keys in mappings.iteritems():
        short = QShortcut(QKeySequence(keys), iface.mainWindow())
        short.setContext(Qt.ApplicationShortcut)
        short.activated.connect(partial(activated, dock))
        shortcuts.append(short)

bind()

and now you can hide and show using ALT + number

Using Hy, a dialect of Lisp for Python, with QGIS


So tonight I rediscovered Hy. I had seen Hy before a while ago but never really sat down and tried it. Tonight just must have been one of those days to try something new.

So Hy is a dialect of Lisp but embedded in Python which means you can use any Python library will using a Lisp dialect. Pretty nifty.

My next thought was, how would this look using the QGIS libraries. So lets give it a try.

First we need to install Hy:

pip install Hy

Now just create a .hy file and add some code

(import qgis)
(import [qgis.core [QgsVectorLayer]])
(import [qgis.core.contextmanagers [qgisapp]])

(setv layers [])

(defn load-layer [file name]
    (setv layer (QgsVectorLayer file name "ogr"))
    (.append layers layer))

(defn print-layer [layer]
    (print "Layer Name:" (.name layer))
    (print "Valid:" (.isValid layer))
    (print "Extents:" (.toString (.extent layer))))

(defn main [app]
    (load-layer r"F:gis_datatest.shp" "test")
    (for [layer layers] (print-layer layer)))


(with [[app (apply qgisapp [] {"guienabled" False})]]
    (print "Loading QGIS")
    (main app))

run it in our shell and bingo.

F:devhy-qgis>hy qgistest.hy
Loading QGIS
Layer Name: test
Valid: True
Extents: 392515.3457026787800714,6461581.2076761415228248 : 392683.3794420150225051,6461705.1012571481987834

Sweet.

Just for reference the Python version of the above would be:

import qgis
from qgis.core import QgsVectorLayer
from qgis.core.contextmanagers import qgisapp

layers = []

def load_layer(file, name):
    layer = QgsVectorLayer(file, name, "ogr")
    layers.append(layer)

def print_layer(layer):
    print "Layer Name:", layer.name()
    print "Valid:", layer.isValid()
    print "Extents:", layer.extent().toString()

def main(app):
    load_layer(r"F:gis_datatest.shp", "test")
    for layer in layers:
        print_layer(layer)

with qgisappl(guienabled=False) as app:
    main(app)

More readable? No doubt, that is why I love Python, however the strange thing is the first time I looked at Lisp, including Hy, I thought “whoa all those parentheses back it up!1!” but strangely after using it for a while (read: not even a few hours) they don’t seem to be an issue, or not much of one anyway. YMMV.

The cool thing with using Hy is you can still use all the libraries you are used to as the example above shows, PyQt, QGIS, anything.

The other interesting, and pretty funky, thing is that you are able to import .hy files like normal Python files. If you create a file winning.hy you can just import winning into any Python application and it works.

Why bother? Mainly because learning something new is never a bad thing, and you never know what you might pick up.

Check out the Hy for more info on what you can do and how it works

I have also created a hy-qgis GitHub repo for some experiments.

Enjoy!

Serving live tiles from a QGIS project via TileStache


I’m more then likly way behind the 8 ball here, aren’t all the cool kids doing tiles these days, but regardless I thought it was pretty cool to share. The other day I found TileStache a neat little Python app that can generate, cache, and serve tiles from a list of providers. The normal way is via Mapnik (and others) to render a image, there is also a vector provider which can render vector tiles. Nifty.

A while ago I wrote qgis2img which can generate an image for project, or layers, and export it for you. It serves two roles, one is to benchmark a project and layer render times, the other as a simple export tool. I thought it would be pretty cool to be able to export tiles from it but was I never really up for working on the math and all the logic so I left it. Then I found TileStache.

The best part about TileStache, apart from that it’s Python, is that you can make your own providers for it, and the API is dead easy

class Provider:
    def __init__(self, layer):
        self.layer = layer

    def renderArea(self, width, height, srs, xmin, ymin, xmax, ymax, zoom):
        pass

How easy is that! Just implement one method and you are good to go. So that’s what I did. I created a custom provider that will load a QGIS project and render out images. Thanks to the work done by Martin from Lutra Consulting for the multithreaded rendering in QGIS 2.4 this is a hell of a lot easier then it used to be.

Ignoring some of the setup code to create and load the session the whole export logic is in these few lines

   extents = QgsRectangle(xmin, ymin, xmax, ymax)
   settings.setExtent(extents)
   settings.setOutputSize(QSize(width, height))
   layers = [layer.id() for layer in project.visiblelayers()]
   image, rendertime = qgis2img.render.render_layers(settings, layers, RenderType=QgsMapRendererSequentialJob)

with render_layers defined as

def render_layers(settings, layers, RenderType):
    settings.setLayers(layers)
    job = RenderType(settings)
    job.start()
    job.waitForFinished()
    image = job.renderedImage()
    return image, job.renderingTime()

As this is build on top of my qgis2img tool you can see the full code here

Running it is as simple as installing TileStache, cloneing qgis2img, updating tilestache.cfg, and running the server.

$ pip install TileStache
$ git clone https://github.com/DMS-Aus/qgis2img.git
$ cd qgis2img

In tilestache.cfg you can just change the path to the project to render:

{
  "cache": {
    "name": "Test",
    "path": "/tmp/stache"
    },
  "layers": {
    "qgis":
    {
      "provider": {"class": "qgis2img.tilestache:Provider",
                   "kwargs": {"projectfile": "data/test.qgs"}
                  }
    }
  }
}

Then run the server

$ tilestache-server /path/to/tilestache.cfg

Note: The path to the .cfg file seems to have to be the full path. I had issues with relative paths working.

To view the tiles you can load the preview URL that TileStache provides or you can use it in something like LeafLet

<!DOCTYPE html>
<html>
<head>
    <title>QGIS Tiles WOOT!</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
</head>
<body>
    <div id="map" style="position: absolute; top: 0; right: 0; bottom: 0; left: 0;"></div>
    <script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>
    <script>

        var map = L.map('map').setView([51.505, -0.09], 5);

        L.tileLayer('http://10.0.0.11:8080/{id}/{z}/{x}/{y}.png', {
            maxZoom: 18,
            id: 'qgis'
        }).addTo(map);

    </script>
</body>
</html>

And the result is live tiles from a QGIS project.

tiles

Winning!

Some Caveats

  • The provider currently doesn’t use metatiles so labels and points will get chopped at tile edge. I have working code for this but haven’t pushed it yet.

  • I don’t kill the QGIS session that I create. Creating a session for each request was really expensive so I just keep it around.

  • I only load the project once so any changes mean starting and stopping the server. Wouldn’t be hard to add a file watcher for this.

  • It’s just me using this at home for fun, I have no idea on how it would scale, or even if it would, but I would be keen to hear feedback on that theory.

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.