Django Geolocation
Posted: October 31st, 2009 | Author: Giv | Filed under: Python, Tutorials | 2 Comments »One thing I love about Django models is the ability to subclass its methods to add extra functionality without having to write any extra code in admin or view layers.
I have an application where a user can enter an address in the admin section. I want to plot this location on Google Maps later but I don’t want to have to parse the address and do a reverse geolocation lookup in the view layer every time that page is viewed. The best thing to do is to store the lat/long values in the database.
I could do this by messing around with the Django admin templates but I’d rather not even let the user know the geolocation lookup is happening. Besides, what if I want to interact with the DB from the interpreter? The geolocation bit should happen no matter where the database is being used.
This is my model
1 2 3 4 5 6 7 8 9 | class Entry(models.Model): title = models.CharField(max_length=200) description = models.TextField('description', blank=True, null=True) address = models.CharField(max_length=200, blank=True, null=True) postcode = models.CharField(max_length=200, blank=True, null=True) city = models.CharField(max_length=200, blank=True, null=True) country = models.CharField(max_length=100) geo_lat = models.DecimalField('latitude', max_digits=13, decimal_places=10, blank=True, null=True) geo_long = models.DecimalField('longitude', max_digits=13, decimal_places=10, blank=True, null=True) |
In Django admin I don’t show ‘geo_lat’ and ‘geo_lat’. We just ask the user to enter the address, then before saving, we do the lookup, set the lat/long values and then save the model.
Creating a new entry would still be done the same way from either admin, view or interpreter:
1 2 | e = Entry(title='a new entry', address='123 Smith Road', city='London', country='UK') e.save() |
But we are going to hijack the save() method to do some extra work before saving to the database. Let’s create a method that does the geolocation lookup first:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import urllib import urllib2 from django.utils import simplejson as json def get_geo(address): address = urllib.quote(address) url = "http://maps.google.com/maps/geo?q=%s&output=json&oe=utf8&sensor=true_or_false&key=12345" % (address) data = urllib2.urlopen(url) obj = json.loads( data.read() ) if obj['Status']['code'] == 200: data = obj['Placemark'][0]['Point']['coordinates'] else: raise Exception('Invalid address') return data |
We pass the address to Google and if we get a 200 status code, we grab the lat/long values and return them.
Now let’s call this method in our save() subclass (inside the Entry model):
1 2 3 4 5 6 | def save(self): add = "%s, %s, %s, %s" % (self.address, self.postcode, self.city, self.country) geo_data = utils.get_geo(add) self.geo_long = str(geo_data[0]) self.geo_lat = str(geo_data[1]) super(Listing, self).save() # Call the "real" save() method |
This could be improved so instead of throwing and exception for bad addresses we handle it more gracefully by informing the user or at least save the address and ignore the geolocation lookup. But either way, the model is now responsible for doing the extra work before saving the new/updated data.
2 Comments »