Django: Handle Latitude and Longitude Properly

Raphaël Léger
3 min readDec 21, 2019

Goal

After reading this, you should be able to store latitude/longitude locations in your Django backend as well as other geospatial data such as polygons delimiting countries, regions, cities, etc.
You should also be able to make advanced awesome and fast requests on these geospatial data the easy-way.

Pre-requisites

You have a working Django backend with a working Postgres database.

Basic approach

For the sake of simplicity, let us focus on a Django backend that has only one model called Client.

from django.db import modelsclass Client(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)

Now, if you want to add a location to every client, you could just be straight-forward and add a latitude field and a longitude field to the model:

from django.db import modelsclass Client(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
latitude = models.FloatField(min_value=-90, max_value=90)
longitude = models.FloatField(min_value=-180, max_value=180)

Creating a new Client then would be easy: just fill in the latitude and longitude fields and you are good to go.

Though, getting a set of specific clients based on their location: not easy.

You could work your way through it by making requests using sinus, cosinus, earth radius and other fancy stuff.

Or… you could use GeoDjango that is here to to ease you out of any math, to have way more accurate results and to make your requests less prone to errors. How cool is that?

PostGIS + GeoDjango approach

PostGIS

First, we need to enable an extension called PostGIS on the database-end. This extension will be in charge to handle all the geospatial storage and requests.

Connect to your database:

psql -h [database_hostname] -p [database_port] -U [database_user] -l

Then run the following:

CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;

Django needs to know it has to use PostGIS from now on. In order to do that, just update the ENGINE setting in settings.py with the following:

DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
...
}
}

GeoDjango

Now that our database is properly set up, we need to add GeoDjango.

Add the package in settings.py's INSTALLED_APPS. The package is included by default in Django so you do not need to pip install anything more than what you have:

INSTALLED_APPS = [
...,
...,
...,
'django.contrib.gis',
]

Then, instead of adding a latitude field and a longitude field to our model, we can just use a GeoDjango's location field:

from django.contrib.gis.db import models
from django.contrib.gis.geos import Point
class Client(models.Model):
firstname = models.CharField(max_length=100)
lastname = models.CharField(max_length=100)
location = models.PointField(geography=True, default=Point(0.0, 0.0))

How to retrieve all clients within a circle

Now that our geospatial configuration is good to go, here is how to retrieve all the clients within a 5-kilometer circle that has its center at [-0.2153, 45.6402]:

from django.contrib.gis.measure import Distance
from django.contrib.gis.geos import Point

clients_within_radius = queryset.filter(
location__distance_lt=(
Point(-0.2153 45.6402),
Distance(m=5000)
)
)

Take a glance at this lookups table for more options.

Enhance the format

By default, the Clientmodel will output a location using a format called WKT:

{
"id": 1,
"firstname": "Edmond",
"lastname": "Boubou",
"location": "POINT(-123.0208 44.0464)"
}

This string representation is not easy to manipulate by an api-consumer such as a frontend.

Using GeoJson

Let’s install a dependency called djangorestframework-gis.
It enables the use of a standard format called GeoJSON.

pip install djangorestframework-gis

Link the dependency to Django, in settings.py's INSTALLED_APPS:

INSTALLED_APPS = [
...
'rest_framework_gis'
]

The Clientmodel will now output a location as proper GeoJSON:

{
"id": 1,
"firstname": "Edmond",
"lastname": "Boubou",
"location": {
"type": "Point",
"coordinates": [-123.0208, 44.0464],
}
}

Adding custom properties

It might come handy to add the following two new properties in your Client model if all you care about is the location without even having to wrap it up in a Geojson format:

    @property
def longitude(self):
return self.location.x
@property
def latitude(self):
return self.location.y

The Clientmodel will now output latitude and longitude this way:

{
"id": 1,
"firstname": "Edmond",
"lastname": "Boubou",
"longitude": -123.0208,
"latitude": 44.0464
}

About fixtures

If your app uses Django’s fixtures to populate the database at somepoint and you wonder how you can populate the location property as well, you just need to use the WKT format in your json fixture file:

"location": "POINT(-0.2153 45.6402)"

By the way, if this article helped you, don’t forget to clap & subscribe! 🙂
This helps me know that taking the time to write the article was worth it!

--

--