Adding GeoDjango to Existing Project
November 18, 2011
I have a need to add GeoDjango functionality to an existing Django project. This runs on a Debian Lenny server, and uses a Postgres 8.4 database
Install packages
I needed to install the following packages:
# apt-get install libgeos-3.2.0 proj postgis gdal-bin postgresql-8.4-postgis
You can test this is all installed correctly using a Django python shell:
>>> from django.contrib.gis.gdal import HAS_GDAL >>> print HAS_GDAL True
If you're working on a development machine with the database on a different box, you'll need to do this on the database box only. It's not needed where the web server runs.
Add GIS functionality to database
Next, we need to change the existing database (bbr) so that it has GIS functionality:
# su postgres $ createlang -d bbr plpgsql $ psql -d bbr -f /usr/share/postgresql/8.4/contrib/postgis-1.5/postgis.sql $ psql -d bbr -f /usr/share/postgresql/8.4/contrib/postgis-1.5/spatial_ref_sys.sql $ psql postgres=# \c bbr bbr=# GRANT ALL ON geometry_columns TO PUBLIC; bbr=# GRANT ALL ON geography_columns TO PUBLIC; bbr=# GRANT ALL ON spatial_ref_sys TO PUBLIC;
Django Project Changes
You will need to change the database driver in your settings.py. Here I'm using postgres so I need to configure the connection to use postgis.
DATABASES = { 'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', 'NAME': 'database_name', 'USER': 'database_user', 'PASSWORD' : 'password', 'HOST' : 'localhost', 'PORT' : 5432, } }
You will also need to add the following to your list of INSTALLED_APPS
:
'django.contrib.gis',
Django Model Changes
I already have text fields that hold latitude and longitude. I need to convert these to the appropriate GIS data types in the database. South works well for database migration, even for GIS types.
As I'm still messing with GeoDjango, I've added two new data types. One is a standard geometry
type, the second will use the geography
type. This uses more complex maths and has some query limitations, but is more accurate.
from django.contrib.gis.db import models as geomodels from django.contrib.gis.geos import * latitude = models.CharField(max_length=15, blank=True, null=True) longitude = models.CharField(max_length=15, blank=True, null=True) map_location = geomodels.PointField(dim=3, blank=True, null=True) point = geomodels.PointField(dim=3, geography=True, blank=True, null=True)
You will also need to override the objects
attribute so that is is GIS aware.
objects = geomodels.GeoManager()
In the save() method, I'm automatically populating the two new data types from the strings I had before.
def save(self): if self.latitude != None and len(self.latitude) > 0: lString = 'POINT(%s %s)' % (self.longitude.strip(), self.latitude.strip()) self.map_location = fromstr(lString) self.point = fromstr(lString) self.last_modified = datetime.now() super(Band, self).save()
Testing
In order to use the postgis
features with the Django auto test system, you'll need a template_postgis
database. To set this up, do the following:
# su postgres $ createlang -d template_postgis plpgsql $ psql -d template_postgis -f /usr/share/postgresql/8.4/contrib/postgis-1.5/postgis.sql $ psql -d template_postgis -f /usr/share/postgresql/8.4/contrib/postgis-1.5/spatial_ref_sys.sql $ psql postgres=# update pg_database set datistemplate=true where datname='template_postgis'; postgres=# \c template_postgis template_postgis=# GRANT ALL ON geometry_columns TO PUBLIC; template_postgis=# GRANT ALL ON geography_columns TO PUBLIC; template_postgis=# GRANT ALL ON spatial_ref_sys TO PUBLIC;
Distance Calculations
Once this is done, you can add a distance
attribute to Bands that have a latitude longitude. In this example I'm working out the distance of the bands from a fixed point, the band with id 1's location.
lOrigin = Band.objects.filter(id=1)[0] try: lMatchingBand = Band.objects.filter(slug=pBandSlug) if lMatchingBand[0].latitude: lBand = lMatchingBand.distance(lOrigin.point)[0] else: lBand = lMatchingBand[0] except IndexError: raise Http404()
Notice that we decorate the band object with distance
only if latitude is populated, otherwise we don't bother.