Shops near you – geographic features of GeoDjango
Boom on mobile devices and widespread Internet access created a demand for applications aware of user geographic location. Nowadays using modern web frameworks with magical powers you can make such geo-enabled web applications easily. Django offers a special sub-framework called geodjango. There you will find an enormous amount of features of "geographic" nature.
In this article I'll present a simple application that will use a part of GeoDjango to search and display shops closest to given address.
GeoDjango starts in the database (for this application). To keep geographic coordinates in a database it needs a special engine (GEOS) to do it. Django documentation describes cases for various databases, but I'm going to use PostgreSQL, which seems to be very good at it.
We need to have a postgresql server installed. We also need postgis extension. For Debian/Ubuntu and alike systems it will be postgresql-*-postgis, where * is the current PostgreSQL version (for Ubuntu 12.04 postgresql-9.1-postgis).
When you install that extension you will have to configure postgres and create a postgis-enabled database. The longer version is in the documentation. I'll show you a quick path for a Debian/Ubuntu system.
- Open a terminal and switch to postgres user:
sudo -i su postgres cd
- Download (wget) configuration script - create_template_postgis-debian.sh from Django documentation.
- Launch it:
chmod 755 ./create_template_postgis-debian.sh ./create_template_postgis-debian.sh
- Create postgis database:
createdb -T template_postgis DATABASE_NAME
- Set a password for postgres user (if you already didn't do that):
psql DATABASE_NAMEAnd execute:\password postgresSet the new password and you are done.
We have a special database "type", we have a special Django configuration - and now we will use to. I'll show you a simple Django application - a list of shops. Each shop will have a name and an address (street, city). In the model we will also store geographic coordinates - used for maps and for "geographic" queries like filtering/limiting by distance.PointField under location. It's capable of storing coordinates. Also I've defined an additional query manager - "gis". GeoManager allows executing GIS/GEOS queries. Default manager can't do that. If you plan to use GIS queries a lot you can set the GeoManager under "objects".
Now we can launch syncdb and create a table for the model.
"location" field is empty. Most of map services like Google Maps offer geocoding services - they turn address into coordinates. I've used geopy to geocode the address with Google Maps geocoder. And after getting those coordinates I can save them in the database using the "location" field.To add "automatic" geocoding I defined a save method for my model (you can also use a signal):
In the save method I use geopy to geocode the address and if successful I'm assigning a "POINT" object to a PointField :) Just don't switch longitude with latitude.Now if address is correct geocoded points should show up for shops, like for example:
From Python level those points are available as location.x as location.y.
Now we will use the magic of the PointField to make a query that will return shops closest to given address (coordinates). If you would store coordinates in the "old" way as two text fields it wouldn't be so easy to do it...
In our example I'll create a view with a form for address user will search form. The backend code will geocode the address and query for closest shops.The view looks like so:
We have a "home" view mapped under the main / url. It's a typical view with form support. The "new" code is located in get_shops function. In that function I query for shops, but using the GIS manager - models.Shop.gis. Using the "location" field I filter the query to return shops not further away than 10 km, and filtered by distance to given address (geocoded to coordinates). Used methods are just a few from many available in GeoDjango. More detailed presentation is available on chicagodjango.com.And there is also a template: And a form class: And now if I enter an address close to some existing shops I'll get them on the list:
It was a bit long way to get such a nice feature, but with the help of Django it wasn't messy or hard.
A simple list of shops may not be a cool presentation of results. As we have coordinates we can use a map system like Google Maps or OpenStreetMap to present shops and queried address on a map.
I've added longitude/latitude of queried address to the template context. After that I used such code to display a Google Map with all shops marked on the map:It's a very very "quick" use of Google Maps API. I've created a map, centered it on queried address coordinates and added markers for given address and all returned shops. In production code this would be in a JS file, and the data with shops would be passed in for example JSON format via AJAX request (or JS function arguments). Google Maps API is described on developers.google.com/maps/.