Creating and Using Custom Spatial Data with Django, Postgres, and GeoDjango

At Talentpair, we decided it would be helpful to track the commute preferences for our candidates. City dwellers often have opinions about which neighborhoods they’re willing to commute to, and knowing these commute preferences allows us to make better matches for candidates and jobs. This post will cover how to:

  • Create a minimal database table
  • Create shape data using Google Earth
  • Extract the shape data for storage
  • Add the shape data to a Postgres database using Django’s GeoDjango extension
  • Query the shape data

I’m going to assume you have a Postgres database with PostGIS set up, and a Django application with GeoDjango installed.

Determining the space and placement of your geospatial data is an arbitrary decision that you will have to make on your own. With that in mind, let’s skip to the first step.

Create a minimal database table

Below is a minimal model which will suit the purposes of this tutorial. We will give our areas a name, and the shape of the area is stored in a MultiPolygonField. The difference between this table and your average Django table is that we are importing models from the GeoDjango module rather than django.db.

from django.contrib.gis.db import models

class Area(models.Model):
    name = models.CharField(max_length=50)
    mpoly = models.MultiPolygonField()

As usual, be sure to makemigrations and then migrate.

Create shape data using Google Earth

First things first, you will need to download the Google Earth desktop application. It may be possible to download shape data from the web version, but this tutorial assumes you are using the desktop app.

Once you have the desktop app installed, open it and search for the general vicinity of where you would like to create your custom areas. The search box is in the upper left of the UI. You can drag the map around and zoom in on your desired location. Once you have found your spot, click the polygon tool above the map (indicated by the red arrow below).

A dialog will appear. With the dialog open (if you close it then you cancel the operation), draw your area and name of the shape. Then click “Ok”. The shape you created will appear in the “Places” section of the Google Earth UI, as indicated in the screen shot below.

Next, right click your new shape and select “Save As”. Save the file as a .kml file.

Extract the shape data for storage

The first step in processing the data is to extract it from the .kml file. To do this, you will need ogr2ogr installed. If you are on a Mac, you can easily install it using Homebrew:

brew install ogr2ogr

Once ogr2ogr is installed navigate to your .kml file using your terminal. Once there, use this command to extract the data (be sure to replace the dummy names with the name of your file and the name you would like for your extracted data):

ogr2ogr -f 'ESRI Shapefile' output-file-name.shp 'original-file-name.kml'

Now you should have four files in addition to the original .kml file. If I ran the command above with the output file name of my-shape.shp, the four new files will be as follows:

  • my-shape.dbf
  • my-shape.prj
  • my-shape.shp
  • my-shape.shx

Keep these files as siblings in whatever directory you are in, or create a new directory for these files. The important part is that you do not separate them because the .shp pulls data from the other files.

Add the shape data to a Postgres database using Django’s GeoDjango extension

Now that we the extracted shape data, we need to write a script to add the data to the Area table. The script below is rather basic, but it gives you the general idea of how to import shape data. Please read the gotchas outlines in the code comments.

from django.contrib.gis.gdal import DataSource
from django.contrib.gis import geos
from mymodule.models import Area

def import_shape_data():
    filenames = [

    for filename in filenames:
        # import the data
        shape_data = DataSource(f"/path/to/directory/{filename}.shp")

        # These shapes are polygons with Z dimensions, and we do not want the Z dimension.
        # The code below removes the Z dimension. It must be done on a clone, because 
        # the original cannot be modified.
        shape_data_poly = shape_data[0][0].geom.clone()
        shape_data_poly.coord_dim = 2

        # create mulitpolygons from the original polygon shapes
        # srid is the id of the type of shape data we want
        shape_data_mpoly = geos.MultiPolygon(shape_data_poly.geos, srid=4326)

        # add a row to the table
        Area.objects.create(name=filename, mpoly=shape_data_mpoly)

    print("Import finished")

if __name__ == "__main__":

Query the shape data

Let’s assume you are storing locations on a table with columns for latitude and longitude coordinates. Here is how you can query to see if the location is within the boundaries of one of your shapes using the mpoly__contains operation.

from django.contrib.gis.geos import Point
from mymodule.models import Area

# longitude is first, then latitude
point = Point(address.lng,

if Area.objects.filter(mpoly__contains=point):
    print("The point is inside one or more areas.")

If you would like to know which of your shapes (if any) are within 20 miles of a given area, the following query using the mpoly__distance_lte does the trick. (lte is “less than or equal to”, the docs linked to below outline more operations like this one.)

from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance
from mymodule.models import Area

# longitude is first, then latitude
point = Point(address.lng,
areas_within_20_miles  = Area.objects.filter(mpoly__distance_lte=(point, Distance(mi=20)))

if areas_within_20_miles:
    print("There are one or more areas within 20 miles of the point.")

Checkout these links for more information about spatial queries and distance queries.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.