Skip to main content

Django GIS

Introduction to GeoDjango

GeoDjango is a powerful extension to Django that turns it into a world-class geographic web framework. It allows you to work with spatial data - data that represents objects in geographic space - just as easily as you work with traditional data types in Django.

Geographic Information Systems (GIS) enable developers to create applications that can:

  • Store and retrieve geographic information
  • Display interactive maps
  • Perform spatial queries (like "find all stores within 5 miles of a user's location")
  • Analyze spatial data (distance calculations, area measurements, etc.)

Whether you're building a location-based service, a mapping application, or any project that deals with geographic data, GeoDjango provides the tools you need.

Prerequisites

Before diving into GeoDjango, you should have:

  • Basic understanding of Django models, views, and templates
  • Familiarity with Python
  • A working Django development environment
  • Spatial database (PostgreSQL with PostGIS is recommended)

Setting Up GeoDjango

1. Install Required Dependencies

GeoDjango relies on several spatial libraries. For Ubuntu/Debian systems:

bash
sudo apt-get install binutils libproj-dev gdal-bin python3-gdal

For macOS using Homebrew:

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

2. Configure Database

GeoDjango requires a spatial database backend. PostgreSQL with PostGIS extension is highly recommended:

python
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'geodjango',
'USER': 'geo_user',
'PASSWORD': 'geo_password',
'HOST': 'localhost',
'PORT': '5432',
}
}

3. Add GeoDjango to Installed Apps

python
# settings.py
INSTALLED_APPS = [
# ... other apps
'django.contrib.gis',
# your project apps
]

Creating Spatial Models

GeoDjango provides spatial field types for representing geographic data:

python
from django.contrib.gis.db import models

class City(models.Model):
name = models.CharField(max_length=100)
population = models.IntegerField()
# Here's where the magic happens - a spatial field
location = models.PointField()

def __str__(self):
return self.name

Common GeoDjango field types include:

  • PointField: A single point (longitude, latitude)
  • LineStringField: A sequence of points forming a line
  • PolygonField: A closed sequence of points forming an area
  • MultiPointField: A collection of points
  • MultiLineStringField: A collection of line strings
  • MultiPolygonField: A collection of polygons
  • GeometryCollectionField: A collection of geometry objects

Working with Spatial Data

Creating and Saving Spatial Objects

You can create spatial objects using Well-Known Text (WKT) format:

python
from django.contrib.gis.geos import Point
from myapp.models import City

# Create a point representing New York City
point = Point(-74.0060, 40.7128) # (longitude, latitude)

# Create and save a new City object
nyc = City(name="New York", population=8800000, location=point)
nyc.save()

Spatial Queries

GeoDjango provides powerful spatial query methods:

python
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
from myapp.models import City

# Find all cities within 50 miles of a point
user_location = Point(-73.9857, 40.7484) # Times Square
nearby_cities = City.objects.filter(
location__distance_lte=(user_location, D(mi=50))
)

# Order cities by distance from a point
cities_by_distance = City.objects.distance(user_location).order_by('distance')

# Find cities within a bounding box
cities_in_area = City.objects.filter(
location__within=some_polygon
)

Common spatial lookups include:

  • contains: checks if geometry contains another
  • within: checks if geometry is within another
  • intersects: checks if geometries intersect
  • distance_lt, distance_gt: distance comparisons
  • dwithin: checks if within a specified distance

Displaying Maps with GeoDjango

Let's create a simple map view to display our cities:

1. Create a view

python
from django.views.generic import TemplateView
from .models import City

class CityMapView(TemplateView):
template_name = 'map.html'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['cities'] = City.objects.all()
return context

2. Create a template with map using Leaflet.js

html
{% extends "base.html" %}
{% load static %}

{% block content %}
<div id="map" style="width: 800px; height: 600px;"></div>

<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />

<script>
// Initialize map
var map = L.map('map').setView([40, -95], 4);

// Add base map layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

// Add markers for cities
{% for city in cities %}
L.marker([{{ city.location.y }}, {{ city.location.x }}])
.addTo(map)
.bindPopup('{{ city.name }} - Population: {{ city.population }}');
{% endfor %}
</script>
{% endblock %}

Advanced: GeoDjango and REST APIs

To build a GIS web application, you'll often need to serve spatial data via an API. Django REST Framework with djangorestframework-gis makes this easy:

1. Install required packages

bash
pip install djangorestframework djangorestframework-gis

2. Create serializers for your spatial models

python
from rest_framework import serializers
from rest_framework_gis.serializers import GeoFeatureModelSerializer
from .models import City

class CitySerializer(GeoFeatureModelSerializer):
class Meta:
model = City
geo_field = 'location'
fields = ('id', 'name', 'population', 'location')

3. Create API views

python
from rest_framework import viewsets
from .models import City
from .serializers import CitySerializer

class CityViewSet(viewsets.ReadOnlyModelViewSet):
queryset = City.objects.all()
serializer_class = CitySerializer

4. Configure URLs

python
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CityViewSet

router = DefaultRouter()
router.register(r'cities', CityViewSet)

urlpatterns = [
path('api/', include(router.urls)),
# other URL patterns
]

This API will serve your city data in GeoJSON format, which can be directly consumed by most mapping libraries like Leaflet or Mapbox.

Real-World Example: Store Locator Application

Let's build a simple store locator application using GeoDjango:

Models

python
from django.contrib.gis.db import models

class StoreCategory(models.Model):
name = models.CharField(max_length=50)

def __str__(self):
return self.name

class Store(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=255)
location = models.PointField()
category = models.ForeignKey(StoreCategory, on_delete=models.CASCADE)
opening_hours = models.CharField(max_length=100, blank=True, null=True)
website = models.URLField(blank=True, null=True)
phone = models.CharField(max_length=20, blank=True, null=True)

def __str__(self):
return self.name

View for finding nearby stores

python
from django.contrib.gis.geos import Point
from django.contrib.gis.db.models.functions import Distance
from django.shortcuts import render
from .models import Store

def find_nearby_stores(request):
# Get parameters from request
latitude = request.GET.get('lat', None)
longitude = request.GET.get('lng', None)
radius = request.GET.get('radius', 5) # default 5km
category_id = request.GET.get('category', None)

if latitude and longitude:
user_location = Point(float(longitude), float(latitude), srid=4326)

# Base queryset
queryset = Store.objects.all()

# Filter by category if provided
if category_id:
queryset = queryset.filter(category_id=category_id)

# Annotate with distance and filter by radius
stores = queryset.annotate(
distance=Distance('location', user_location)
).filter(
distance__lte=radius * 1000 # Convert km to meters
).order_by('distance')

return render(request, 'stores/nearby.html', {
'stores': stores,
'latitude': latitude,
'longitude': longitude,
'radius': radius,
})
else:
# Handle case where coordinates aren't provided
return render(request, 'stores/search.html')

Template showing stores on a map

html
{% extends "base.html" %}
{% load static %}

{% block content %}
<div class="container">
<div class="row">
<div class="col-md-4">
<h2>Nearby Stores</h2>
<ul class="store-list">
{% for store in stores %}
<li class="store-item" data-id="{{ store.id }}">
<h3>{{ store.name }}</h3>
<p>{{ store.address }}</p>
<p><strong>Distance:</strong> {{ store.distance.km|floatformat:2 }} km</p>
{% if store.phone %}
<p><strong>Phone:</strong> {{ store.phone }}</p>
{% endif %}
</li>
{% empty %}
<li>No stores found in this area.</li>
{% endfor %}
</ul>
</div>
<div class="col-md-8">
<div id="map" style="width: 100%; height: 600px;"></div>
</div>
</div>
</div>

<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />

<script>
// Initialize map centered on user location
var map = L.map('map').setView([{{ latitude }}, {{ longitude }}], 12);

// Add OpenStreetMap layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

// Add user location marker
var userIcon = L.icon({
iconUrl: '{% static "img/user-marker.png" %}',
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32]
});

L.marker([{{ latitude }}, {{ longitude }}], {icon: userIcon})
.addTo(map)
.bindPopup('Your Location')
.openPopup();

// Add store markers
{% for store in stores %}
L.marker([{{ store.location.y }}, {{ store.location.x }}])
.addTo(map)
.bindPopup(
'<strong>{{ store.name }}</strong><br>' +
'{{ store.address }}<br>' +
'Distance: {{ store.distance.km|floatformat:2 }} km'
);
{% endfor %}

// Draw radius circle
L.circle([{{ latitude }}, {{ longitude }}], {
radius: {{ radius }} * 1000,
fillColor: '#0078D7',
fillOpacity: 0.1,
color: '#0078D7',
weight: 1
}).addTo(map);
</script>
{% endblock %}

Summary

GeoDjango transforms Django into a powerful framework for building geospatial web applications. In this tutorial, we've covered:

  1. Setting up GeoDjango with PostGIS
  2. Creating spatial models with geometry fields
  3. Performing spatial queries
  4. Displaying maps using Leaflet.js
  5. Building a RESTful API for spatial data
  6. Creating a real-world store locator application

With GeoDjango, you can build sophisticated location-based applications including:

  • Store locators
  • Real estate mapping applications
  • Asset tracking systems
  • Delivery and logistics platforms
  • Social applications with location features
  • And much more!

Additional Resources

Exercises

  1. Create a model for national parks that includes their boundaries as polygon data.
  2. Implement a "find parks within X miles" feature using spatial queries.
  3. Add a feature to your store locator to filter stores by type.
  4. Extend the store locator to calculate and show directions from the user's location to a selected store.
  5. Create a heatmap visualization showing the density of stores across a region.

GeoDjango opens up a world of possibilities for spatial data applications. With the foundation provided in this tutorial, you're ready to start building your own location-based services.



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)