Using GeoDjango to Filter by Points

Just recently I found myself playing with GeoDjango , I’ve been using it on both a Ubuntu 14.04 cloud server and a Macbook Pro (OS X El Capitan).

GeoDjango allows us to query by geographic points directly on the data model. We are then able to extend the model, and add a custom method to search by zipcode.

Using the Django shell we can easily check data in our favorite interpreter :

$ ./manage.py shell

In [1]: from hub.models import Vendor

In [2]: Vendor.get_vendors(zipcode='78664', miles=5)
Out[2]: [<Vendor: Starbucks>]

In [3]: Vendor.get_vendors(zipcode='78664', miles=10)
Out[3]: [<Vendor: Starbucks>, <Vendor: Starbucks>,
<Vendor: Starbucks>, <Vendor: Starbucks>,
<Vendor: Starbucks>, <Vendor: Starbucks>, <Vendor: Starbucks>]

It’s then pretty easy to take that data and present it on a Google Map ( using the Django application’s views and templates ):

If you find any of this exciting; read on, I’m going to go over setting the environment up from scratch ( using a Macbook as the development environment).

Prerequisites

Setup

It is a good idea to add Postgres.app ’s bin path to your $PATH .

You should run the following command (changing the version to match your install), and add it to the bottom of your ~/.bash_profile :

export PATH=$PATH:/Applications/Postgres.app/Contents/Versions/9.4/bin

Next lets create our PostgreSQL database, and enable the GIS extension.

Start the Postgres.app OSX application. Next click the elephant from your upper task bar, and select Open psql .

nessy=# create database geoapp;
CREATE DATABASE

nessy=# \c geoapp
You are now connected to database "geoapp" as user "nessy".

geoapp=# CREATE EXTENSION postgis;
CREATE EXTENSION

You can now close the psql shell.

Next lets install Django into a virtualenv

# create and change to new app directory
mkdir ~/geoapp && cd ~/geoapp/

# create a fresh virtual environment
virtualenv env

# activate the virtual environment
source env/bin/activate

# install Django inside the virtual environment
pip install Django

To use PostgreSQL with Python we will need the adapter installed, be sure you added Postgres.app’s bin path to your $PATH:

pip install psycopg2

GeoDjango requires the geos server to be available, we can install this with homebrew :

brew install geos

We are now ready to create the Django project and application.

# create a new project using Django admin tool
django-admin startproject geoproject

# change to the newly created project directory
cd geoproject/

# create a new application
./manage.py startapp hub

Now you need to configure your Django application to use PostgreSQL and GIS, open geoproject/settings.py with your favorite text editor.

vim geoproject/settings.py

Append django.contrib.gis and hub to your INSTALLED_APPS:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.gis',
    'hub',
)

Next find the DATABASES portion and set it to the postgis engine:

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': 'geoapp',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': ''
    }
}

The next step will be to create our model using GIS points, add the following to hub/models.py :

from django.contrib.gis.db import models
from django.contrib.gis.geos import Point, fromstr
from django.contrib.gis.measure import D

class Vendor(models.Model):

    def __unicode__(self):
        return unicode(self.name)

    def save(self, *args, **kwargs):
        if self.latitude and self.longitude:
            self.location = Point(float(self.longitude), float(self.latitude))
        super(Vendor, self).save(*args, **kwargs)

    name = models.CharField(max_length=100)
    longitude = models.FloatField()
    latitude = models.FloatField()
    location = models.PointField(blank=True, null=True)

You will also want to add this model to the admin page, so update hub/admin.py :

from django.contrib import admin

from hub.models import Vendor

class VendorAdmin(admin.ModelAdmin):
    list_display = ('name', 'longitude', 'latitude')
    exclude = ('location',)
admin.site.register(Vendor, VendorAdmin)

At this point you are ready to create the database tables, use the provided manage.py script:

./manage.py syncdb

I’m going to now jump into the Django shell to add data, but this can also be done using the admin ( http://127.0.0.1:8000/admin ):

./manage.py shell

In [1]: from hub.models import Vendor

In [2]: Vendor.objects.create(longitude=-97.677580, latitude=30.483176,
   ...: name='Starbucks')
Out[2]: <Vendor: Starbucks>

In [3]: Vendor.objects.create(longitude=-97.709085, latitude=30.518423,
  ...: name='Starbucks')
Out[3]: <Vendor: Starbucks>

In [4]: Vendor.objects.create(longitude=-97.658976, latitude=30.481517,
   ...: name='Starbucks')
Out[4]: <Vendor: Starbucks>

In [5]: Vendor.objects.create(longitude=-97.654141, latitude=30.494810,
   ...: name='Starbucks')
Out[5]: <Vendor: Starbucks>

I can then define a point in the center of the city, and filter by locations within a 5 mile radius:

In [6]: from django.contrib.gis.geos import fromstr

In [7]: from django.contrib.gis.measure import D

In [8]: point = fromstr('POINT(-97.6786111 30.5080556)')

In [9]: Vendor.objects.filter(location__distance_lte=(point, D(mi=5)))
Out[9]: [<Vendor: Starbucks>, <Vendor: Starbucks>, <Vendor: Starbucks>,
<Vendor: Starbucks>]

Hope you found this article helpful; if you did, please share with friends and coworkers.