Will & Skill Developers

Will & Skill Developers


Thoughts, snippets and ideas from the team at Will & Skill AB, Stockholm.

Erik Svedin
Author

Share


Making spatial querys in django using Postgres & PostGIS

Erik SvedinErik Svedin

Spatial querys is a very powerful tool to have in your arsenal, also in django its surprisingly easy to get started! This guide will show you how to install postgis extension on a postgres database as well as making spatial querys using django.

Installing dependencies

Before getting in to the django part make sure the following is installed:

  1. Postgres
  2. postgis
  3. gdal
  4. libgeoip

The easiest way to install postgress locally is through the postgress app (http://postgresapp.com/). It comes with postgis included.

On OSX you can install both gdal and libgeoip via brew:

$ brew install gdal
$ brew install libgeoip

Configuring database

Create a database via the postgres shell and make sure to add the postgis extension:

$ createdb  DATABASE_NAME
$ psql DATABASE_NAME
> CREATE EXTENSION postgis;

Configuring django for PostGIS

In your projects settings.py file you'll need to configure the database options to make sure you're using the 'django.contrib.gis.db.backends.postgis' as database engine. Your config should look like this:

DATABASES = {  
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': DATABASE_NAME,
        'USER': DATABASE_USER,
        'PASSWORD': DATABASE_PASSWORD,
        'HOST': DATABASE_HOST,
        'PORT': '',
    }
}

Also while in your settings.py file make sure you add django.contrib.gis to your INSTALLED_APPS array.

INSTALLED_APPS = [  
    ...
    'django.contrib.gis',
    ...
]

Setting up models with GeoDjango model fields

So far you've installed & configured your environment, database and the django integration. Time to actually do some coding.

Lets consider & try out the following scenario:
You have a class House which have a position.
You want to make querys for all houses in a specific range depending on another given location.
I.e 'give me all houses within 5km'.

First of you'll need to add a PointField to your House class. Notice how we're not using the usual django.db.models, instead we´re using django.contrib.gis.db.models:

from django.contrib.gis.db import models

class House(models.Model):  
    position = models.PointField(null=True, blank=True)
    ...

Check out the docs for all model fields, https://docs.djangoproject.com/en/1.10/ref/contrib/gis/model-api/

Populating the position field can be done by creating a GEOSGeometry Point object and updating a House instance, make sure you're using Decimals when defining you latitude/longitude:

from django.contrib.gis.geos import GEOSGeometry  
from decimal import Decimal

latitude = Decimal(59.32932)  
longitude = Decimal(18.06858)

point = GEOSGeometry("POINT({0} {1})".format(longitde, latitude))

# Just to show how to update a model instance
house = House.objects.get(pk=1)  
house.position = point  
house.save()  

You now set the house with pk=1 to be located in the heart of Stockholm (you can try out the lat/lng in google maps for example).

So how can we actually make querys based on this information? Lets say you want to build a Housing app á tinder and you only want houses within 5km to show up. Easy!

Making querys based on distance

Basically you will only need to define 2 things: Another Point from which we will measure the distance, and the maximum distance which we find acceptable.

Given that we have the latitde/longitude for the point from which we will measure, most probably supplied as parameters to a view of some sorts, this is pretty easy:

from django.contrib.gis.measure import Distance  
from decimal import Decimal

# Also here, make sure that we're dealing with decimal values:
latitude = Decimal(LATITUDE_FROM_REQUEST)  
longitude = Decimal(LONGITUDE_FROM_REQUEST)

# Create a GEOSGeometry POINT and specify your max distance
point_of_user = GEOSGeometry("POINT({} {})".format(longitude, latitude))  
max_distance = 5

# Here we're actually doing the query, notice we're using the Distance class fom gis.measure
houses_in_range = House.objects.filter(  
            position__distance_lte=(
                point_of_user,
                Distance(km=max_distance)
            )
        )

And voila! You've now performed your first spatial query using django. This is just the tip of the iceberg, a lot of cool stuff can be done. For example you may specify a Polygon and get all the Houses within that Polygon area, or outside of it for that matter. Go make something awesome!

Erik Svedin
Author

Erik Svedin

Comments