Chicago Django/Python Blog - Imaginary Landscape

Joe Jasinski

Geo Django Quickstart

Joe Jasinski—Programmer

September 21, 2011

GeoDjango is a very powerful tool for storing and manipulating geographic data using the Django ORM.  It provides a simple API to determine distances between two points on a map, find areas of polygons, locate the points within a polygon, and much more.  

GeoDjango is fairly well documented on the Django website.  My aim with this walkthrough is to reduce the Django documentation to a simple set of steps that will quickly get you started experimenting with GeoDjango.  As a result, some of the initial steps are summarized from the Django site with only slight modifications to fit the circumstances.  To reference the docs directly, visit http://geodjango.org/.

This walkthrough assumes that you are using a fresh install of Ubuntu 11.04.  It will walk you through the install of Django in a Python virtual environment, the configuration of PostgreSQL with the postgis geo-spacial extensions, the set-up of a spacial Django application, and common geo-spacial queries and applications.  

Step 1) Install Ubuntu package dependencies:

# Needed for postgres and the postgis plugin
sudo apt-get install binutils gdal-bin postgresql-8.4-postgis \
    postgresql-server-dev-8.4 python-psycopg2 python-setuptools


# Recommended for isolating python environment
sudo apt-get install python-virtualenv

# Needed for compiling psycopg2 in virtualenv
sudo apt-get install python-dev

Step 2) Set up postgres geo-spacial template:
 
Normally, when a new postgres database is created, it is created as a copy of a template database (called “template1”) which configures a bunch of database defaults.  Before creating a new geo-spacial database, we need to first create a geo-spacial template (called “template_postgis”) that can be copied to create a new geo-spacial database.  

POSTGIS_SQL_PATH=`pg_config --sharedir`/contrib/postgis-1.5
# Creating the template spatial database.
sudo -u postgres createdb -E UTF8 template_postgis
sudo -u postgres createlang -d template_postgis plpgsql # Adding PLPGSQL language support.
# Allows non-superusers the ability to create from this template
sudo -u postgres psql -d postgres -c "UPDATE pg_database SET datistemplate='true' WHERE datname='template_postgis';"
# Loading the PostGIS SQL routines
sudo -u postgres psql -d template_postgis -f $POSTGIS_SQL_PATH/postgis.sql
sudo -u postgres psql -d template_postgis -f $POSTGIS_SQL_PATH/spatial_ref_sys.sql
# Enabling users to alter spatial tables.
sudo -u postgres psql -d template_postgis -c "GRANT ALL ON geometry_columns TO PUBLIC;"
sudo -u postgres psql -d template_postgis -c "GRANT ALL ON geography_columns TO PUBLIC;"
sudo -u postgres psql -d template_postgis -c "GRANT ALL ON spatial_ref_sys TO PUBLIC;"


Step 3) Set up postgres geo-spacial database:
 
After creating a geo-spacial template, we can create a new user and copy the template to create a new geo-spacial database.  For this example, we will create a user called testgis_user and a database called testgis_db.

sudo -u postgres createuser --createdb testgis_user
sudo -u postgres psql testgis_db -c "ALTER USER testgis_user with password '1234'";
sudo -u postgres createdb -T template_postgis -O testgis_user testgis_db
# answer “no” to all the questions


Step 4) Set up Python virtual environment:

Now we can create a new Python virtual environment to isolate our application from the system Python install.  (If you are unfamiliar with virtualenv, check it out at http://pypi.python.org/pypi/virtualenv.)

virtualenv --no-site-packages testgis
cd testgis
. ./bin/activate
pip install django psycopg2


# packages not related to geoDjango specifically, but helpful nonetheless
pip install django-autofield  # handy package for making slugs on a model automatically

pip install django-extensions # handy for creating a UUID model field

Step 5) Set up a new Django project:

For this example, we will create a directory called proj/ and create our new Django project and app in there.  

mkdir proj; cd proj
django-admin.py startproject testgis
python ./manage.py startapp tgis


Step 6) Configure the Django project with postgis support:

To configure the Django project, you will need to set the database connection parameters (per the DATABASES dictionary in the example below).  Note that the ENGINE setting is set to the postgis backend; this enables the postgres geo-spacial features in the Django ORM.   Also, you will need to add  'django.contrib.gis', to your INSTALLED_APPS as well as the django app that we created (‘tgis’).  Optionally, you can add the Django admin to the INSTALLED_APPS setting.

Add to ./testgis/settings.py :

DATABASES = {
   'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': 'testgis_db',
        'USER': 'testgis_user',
        'PASSWORD': '1234',
        'HOST': 'localhost',
    }
}

INSTALLED_APPS = (

   'django.contrib.admin',
   'django.contrib.gis',
   'tgis',
)


Step 7) Create some GIS-enabled Django models:

When defining Geo-spacial Django models, we must use the gis models module, which defines geo-spacial objects that subclass the normal Django model classes.  Notice that the first import below imports django.contrib.gis.db.models rather than django.db.models.  This extended models module defines several new model field types, such as PointField() and PolygonField().  Also note that we must override the default model manager with GeoManager(), otherwise we will not be able to perform any geo-spacial queries.  In the model definition below, I added a slug field and a uuid field simply to provide a few ways to uniquely reference the model instance.

./testgis/proj/testgis/tgis/models.py
# Notice that we are importing the gis models here
from django.contrib.gis.db import models

from autoslug import AutoSlugField
from django_extensions.db import fields as ext_fields

class Location(models.Model):
   name = models.CharField(max_length=255)

   # Automatically create slug based on the name field
   slug = AutoSlugField(populate_from='name', max_length=255)
   
   # Automatically create a unique id for this object
   uuid = ext_fields.UUIDField(auto=True)
   
   # Geo Django field to store a point
   point = models.PointField(help_text="Represented as (longitude, latitude)”)

   # You MUST use GeoManager to make Geo Queries
   objects = models.GeoManager()

class Region(models.Model):
   name = models.CharField(max_length=255)
   slug = AutoSlugField(populate_from='name', max_length=255)
   uuid = ext_fields.UUIDField(auto=True)
   
   # Geo Django field to store a polygon
   area = models.PolygonField()

   # You MUST use GeoManager to make Geo Queries
   objects = models.GeoManager()


Step 8) Create GIS-enabled admin:

When defining ModelAdmin classes, you can instead use the GeoModelAdmin subclass to display the data stored in geometry model fields.  On the Admin change page, the geographic data will be displayed on top of a Google-maps-like map that you can scroll and zoom.

./testgis/proj/testgis/tgis/admin.py
from django.contrib.gis import admin
from . import models

class LocationAdmin(admin.GeoModelAdmin):

search_fields = ['name','uuid']
list_display = ['uuid','name','point',]
readonly_fields = ['uuid','slug',]

admin.site.register(models.Location, LocationAdmin)

class RegionAdmin(admin.GeoModelAdmin):

search_fields = ['name','uuid']
list_display = ['uuid','name']

admin.site.register(models.Region, RegionAdmin)

As usual, you can enable the admin by adding the following to the root level urls.py
./testgis/proj/testgis/urls.py

from django.conf.urls.defaults import patterns, include, url

from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
)

Step 9) Sync the database:

python ./manage.py syncdb

Step 10) Enter some geo-spacial points:

from tgis.models import Location, Region
from django.contrib.gis.measure import D
from django.contrib.gis.geos import (Point, fromstr, fromfile,
               GEOSGeometry, MultiPoint, MultiPolygon, Polygon)

Define some points that can be imported.  In this case, I’ve created a list of tuples where each tuple contains the name of a location, the location’s latitude and its longitude.

location_data = [('NicNonnalds', '-87.627675', '41.881925'), ('Boundaries Books', '-87.6281729688', '41.881849562'), ('Field Marshal Department Store', '-87.62839', '41.88206'), ('RadioShock', '-87.6269801114', '41.8814058757'), ('CAN Insurance', '-87.6266873845', '41.8818595588'), ('SuperWay Sandwiches', '-87.6266580795', '41.8813617549'), ('911 Convenience Store', '-87.6285777569', '41.8810785557'), ('Nobel Barnes Books', '-87.627834', '41.880856'), ('Decade Park', '-87.62929387', '41.88207029'), ('Burrito Bell', '-87.6282415079', '41.8830285557'), ('Seals Tower', '-87.627696', '41.880745'), ('Lake Hotel', '-87.627696', '41.880745'), ('Weekly Plaza', '-87.627696', '41.880745'), ('Forest Museum', '-87.62749695', '41.88316957'), ]

Loop through the points and insert them into the Django Location model that we defined.  We first create point objects by defining the points as a string (i.e. a point could be represented as a string such as “POINT(-87.6282415079 41.8830285557)” ) and convert the point string to an object using the ‘fromstr’ function.  Note that the point strings list the longitude before the latitude and there is only a space between the longitude and latitude values (not a coma).  

for location in location_data:
  point = fromstr("POINT(%s %s)" % (location[1], location[2]))
  location_obj = Location(name=location[0], point=point)
  location_obj.save()

Define a polygon to import.  Here we define a data structure filled with longitude/latitude coordinates, convert them to point objects, append each point to a list of points, and create a polygon using the point list as an input.
IMPORTANT: when defining points for the polygon, the polygon must be a closed polygon. This means that the first point must be the same as the last point.

a1 = (((-87.6705551147461, 41.89135645852043),(-87.64171600341797, 41.89288988217029),(-87.63690948486328, 41.880110226947934),(-87.66368865966797, 41.87806524488436),(-87.68016815185547, 41.887267148816726), (-87.6705551147461, 41.89135645852043)),)

plist = []
for p in a1[0]:
   plist.append( GEOSGeometry('POINT(%s %s)' %(p[0], p[1])))

p = Polygon(plist)

An alternative syntax to declare the same polygon would be to define it as a string. Again, be sure that points that you specify give the longitude before the latitude.

poly="""POLYGON((-87.6705551147461 41.89135645852043, -87.64171600341797 41.89288988217029, -87.63690948486328 41.880110226947934, -87.66368865966797  41.87806524488436, -87.68016815185547 41.887267148816726, -87.6705551147461 41.89135645852043))"""

p = GEOSGeometry(poly)

Then save the polygon object to a region model.
r = Region(name='Region 1', area=p)
r.save()


Step 11) Run some geo-spacial point queries:

a) Create a reference point that will be used as the center of our search.  We will look for other points around this reference point.
from django.contrib.gis.geos import fromstr
lat = "41.881944"
long = "-87.627778"

# NOTE: A point takes the format "POINT(longitude latitude)".  Please be careful as it's easy to mix up the order of longitude and latitude.
ref_pnt = fromstr("POINT(%s %s)" % (long, lat))

b) Define a search radius of 100 meters from the reference point.  We will find neighboring points that fall within this radius.
distance_from_point = {'m':'100'}

c) Search for locations closer than 100 meters from reference point.
close_locations = Location.objects.filter(point__distance_lte=(ref_pnt, D(**distance_from_point) ))

d) Search for locations closer than 100 meters from reference point, sorted by distance.
close_locations_sorted = Location.objects.filter(point__distance_lte=(ref_pnt, D(**distance_from_point) )).distance(ref_pnt).order_by('distance')

e) Search for locations further than 100 meters from reference point, sorted by distance.
far_locations = Location.objects.filter(point__distance_gte=(ref_pnt, D(**distance_from_point) )).distance(ref_pnt).order_by('distance')

f) Print the located locations and the distances.
for l in close_locations_sorted.distance(ref_pnt):
  print "%s - %s" % (l.name, l.distance)


Step 12) Run some geo-spacial region queries:
region = Region.objects.all()[0]

a) Determine the geometry type of the region.  In this case, it's a polygon.
region.area.geom_type

b) Output this coordinates for this region.
region.area.coords

c) Determine if the reference point is inside the region.
region.area.contains(ref_pnt)

d) Find the polygon boundary.  
region.area.boundary

e) Find the approximate center of the region.
region.area.centroid

f) Find the area of the region.
region.area.area

g) Find the number of points in the region.
region.area.num_points

h) Find the convex hull of the region.  Useful to simplify complex calculations.
region.area.convex_hull

i) Output this region in KML format.
region.area.kml

j) Print regions in order of least number of points in the polygon to most.
for r in Region.objects.all().num_points().order_by('num_points'):
  print r, r.num_points


Applications:

GeoDjango and specialized data can be utilized to make websites more useful and informative.  Some possible applications might be to find businesses located around a transit station, to determine within what zip code or neighborhood an address is located, or to search for nearby branch offices of a business.  Android and IOS mobile devices, with their ability to pinpoint their exact location, open up new possibilities when combined with a GeoDjango-backed web-service.  

Hopefully this tutorial has provided you with some basic steps to get GeoDjango up and running.  If you have any questions or comments, please feel free to include them below.

 


Updated 09/21/11 @ 12:37PM CDT by jjasinski

Bookmark and Share

Categories: Django Python

Tags: django geo geographic geospatial latitude longitude python

(877) 275-9144 Toll Free | M-F:9-5

Imaginary Landscape, LLC 5121 North Ravenswood Avenue Chicago, Illinois 60640
The domain chicagodjango.com is used with the permission of the Django Software Foundation. Imaginary Landscape LLC is not an official representative of the Django Software Foundation or the Django open source software project.