Location-based application using Django and PostGIS

One spatial point to an other — Popeye the Sailor.

In a prior post, the experiment on hybrid spatial-index to find spatial data points is worth exploring, but the in-memory implementations and the lack of ability to scale make it almost pointless to use it in real-world applications.

In a web application that requires storage and accessing spatial data (co-ordinates), a combination of Django, PostgreSQL (PostGIS), GeoDjango, and Leaflet (or Google Maps) solves most of the preliminary use cases.

The Local Set-up

  • Install Dependencies
  • Start PostgreSQL Server
  • Configure Django Application

Install virtualenv

As always, when working on python projects, always use virtualenv or conda env. If you don’t have virtualenv already installed, install it (Reference):

python3 -m pip install --user --upgrade pip
python3 -m pip --version
python3 -m pip install --user virtualenv

Install Dependencies

Create an environment for the project, replace env with the appropriate name:

python3 -m venv env
source env/bin/activate
python3 -m pip install -r requirements.txt

The contents of requirements.txt:

Django==4.0.2
django-leaflet==0.28.2
psycopg2-binary==2.9.3
gunicorn==20.1.0

Of course, you don’t need gunicorn here, just a practice to remember to not use the development server in production (which I often see in other blog posts).

Install Prerequisites

PostGIS is a spatial database extender for PostgreSQL object-relational database. It supports geographic objects allowing location queries to be run in SQL.

brew install postgresql
brew install postgis
brew install gdal
brew install libgeoip

Make sure psycopg2-binary is already installed as mentioned in requirements.txt

Start PostgreSQL server

While you can install PostgreSQL server locally, using docker makes it a lot easier without any hassle apart from ensuring docker is installed.

Start docker in your local machine and run:

docker run --name=postgis -d -e POSTGRES_USER=<database-username> -e POSTGRES_PASS=<database-password> -e POSTGRES_DBNAME=<database-name> -p 5432:5432 kartoza/postgis:14-3.2

and replace <database-username> and <database-password>, <database-name> as per your needs.

Django Project Set-up

Configure PostGIS database, INSTALLED_APPS, and Leaflet in settings.py

DATABASES

Assuming that you already have a Django project handy, in settings.py, the database:

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': config('DATABASE_NAME'),
        'USER': config('DATABASE_USER'),
        'PASSWORD': config('DATABASE_PASSWORD'),
        'HOST': config('HOST_ENDPOINT'),
        'PORT': '5432',
    }
}

If you are using the Mac M1, you’ll need to add the full path to gdal, for local set-up, simply add the below two files in settings.py

GDAL_LIBRARY_PATH = '/opt/homebrew/opt/gdal/lib/libgdal.dylib'
GEOS_LIBRARY_PATH = '/opt/homebrew/opt/geos/lib/libgeos_c.dylib'

INSTALLED_APPS

Add django.contrib.gis to INSTALLED_APPS in settings.py. If you are using Leaflet, install django-leaflet and add leaflet to INSTALLED_APPS in settings.py.

LEAFLET_CONFIG

Add LEAFLET_CONFIG configuration in settings.py

LEAFLET_CONFIG = {
    'DEFAULT_CENTER': (44.638569, -63.586262),
    'DEFAULT_ZOOM': 18,
    'MAX_ZOOM': 20,
    'MIN_ZOOM': 3,
    'SCALE': 'both',
    'ATTRIBUTION_PREFIX': 'Location Tracker'
}

Lastly, Leaflet might need static; make sure to add the path in settings.py:

STATIC

static file(s) relative path from the root directory:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

Django Models

Let’s say you have a model called Trip, which has the source and destination address (co-ordinates: Latitude and Longitude).

class Trip(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    source_location = models.PointField(null=True)
    destination_location = models.PointField(null=True)

in admin.py:

from leaflet.admin import LeafletGeoAdmin
from .models import Trip

class TripAdmin(LeafletGeoAdmin):
    list_display = ('source_location', 'destination_location')

admin.site.register(Trip, TripAdmin)

Run migrations, Create a superuser and Start the development server

python3 manage.py makemigrations
python3 manage.py migrate

python3 manage.py createsuperuser

python3 manage.py runserver

Finally, head to http://localhost:8000/admin, enter the username and password, and navigate to the model (Trip), which has the location fields, to create an entry.

Code Snippet

To search for trips for source_location within a radius

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

def get_trips(latitude, longitude, radius):
    point = Point(latitude, longitude)
    trips = Trip.objects.filter(source_location__distance_lt=(point, Distance(km=radius)))
    return trips

That’s about it! While this is not a complete list of features of GeoDjango, refer to the documentation here: https://docs.djangoproject.com/en/4.0/ref/contrib/gis/tutorial/# for more details.

As always, the reference code: https://github.com/addu390/dedo

Cite this article as: Adesh Nalpet Adimurthy. (Mar 15, 2022). Location-based application using Django and PostGIS. PyBlog. https://www.pyblog.xyz/django-postgis

#index table of contents