Geo Django Raster Metadata

I’ve been using GeoDjango for some time now, and found myself in a situation where I wanted to retrieve raster metadata using the ST_MetaData function on some datasets stored in PostGIS.

To do so, you can of course use the raw function from the db Manager, but for this I went the extra mile (although, it’s more like the extra half-mile, since it’s very easy)

On Func and GeoFunc

GeoDjango provides django.contrib.gis.db.models.functions.GeoFunc and Django provides django.db.models.Func to create extra functions not available on the framework, or simply to write special functions for your project.

A first instinct tells us to use GeoFunc, but on a closer inspection it seems that it is only suitable for Geometric fields, which is not the case for a Raster. In this case we can use a simple Function and parse the results, because the result of the function we want to implement (ST_MetaData) contains no raster it will be easy to parse.

Calling ST_Metadata

This is fairly clear and direct.

from django.db.models import Func

class ST_RasterMetadata(Func):
    """
    Returns Raster metadata of a raster_field
    """

    arity=1
    function='ST_MetaData'

Creating this class, we now have a function that can be called in a queryset, expecting one parameter (the raster).

MyRasters.objects.annotate(
   metadata=ST_RasterMetadata('raster_field')
)

As a simple note, the arity of the function is 1, since it only receives one parameter ('raster_field').

But that’s not enough…

Parsing ST_Metadata

The result from the previously created function leaves much to be desired, so what we’ll do is parse it in a dictionary.

The ST_Metadata function returns a set of values, as stated in the docs:

rid  | upperleftx | upperlefty | width | height | scalex | scaley | skewx | skewy | srid | numbands
 ----+------------+------------+-------+--------+--------+-----------+-------+-------+------+-------
   1 |        0.5 |        0.5 |    10 |     20 |      2 |      3 |		0 |     0 |    0 |        0
   2 | 3427927.75 |    5793244 |     5 |      5 |   0.05 |  -0.05 |		0 |     0 |    0 |        3

My end goal is to store the returned values in a dictionary with the proper keys. To do so, we need to create our own Field that knows how to handle the return, and link it to our PG_RasterMetadata function.

Since the default return is a treated as a string we can subclass our field from CharField and work from there.

On the version that I’m using, I end up with a response like: (92.3,24.5,300,1233,1.0,1.7,0.4,0.5,4326,1) (note that I totally made up those values), corresponding to (upperleftx| upperlefty| width| height| scalex| scaley| skewx| skew| srid| numbands).

To get the values instead of strings, it’s a matter of removing the parentheses, and casting. In a simple line:
tuple(map(float, value[1:-1].split(',')))

This is exactly what we have write on the from_db_value function of our MetadataField:

from django.db.models import Func

class ST_MetaDataField(models.CharField):
    def from_db_value(self, value, expression, connection):

        if not value:
            return None

        return tuple(map(float, value[1:-1].split(',')))

This will end up with the tuple and the values. Since we already know what each value is, we can map them to an appropriate dictionary, create pairs using zip and generate a dictionary from it.

from django.db.models import Func

class ST_MetaDataField(models.CharField):
    def from_db_value(self, value, expression, connection):

        if not value:
            return None

        parsed_dbdata = tuple(map(float, value[1:-1].split(',')))

        output = {
                d[0] : d[1]
                for d in
                zip(('upperleftx', 'upperlefty', 'width',
                    'height', 'scalex', 'scaley', 'skewx',
                    'skew', 'srid','numbands'),
                    parsed_dbdata)
        }

        return output

And finally, the newly created class has to be assigned to the ST_RasterMetadata class as it’s output field.

class ST_RasterMetadata(Func):
    arity=1
    function='ST_MetaData'
    output_field=ST_MetaDataField()

As stated at the beginning, this is quite easy to achieve and helps us keep our code clean from calls to raw, very neat!.

References

Advertisements

Leave a comment

Filed under code, curious, tips

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.