Making a Simple Django App: Calculating the Distance between Two Airports

Source Code for a Django App to Calculate the Distance between Any Two U.S. Airports
by Oliver; June 3, 2014
   
web
 

The Problem

A few days ago I was given the following problem: make a web app that calculates the distance between any two airports in the U.S. The problem also specified that the form fields should auto-complete. What follows is a simple solution to this problem, coded in Django.

The Data

This problem isn't going to work without a dataset, so where do we find one? Luckily, there's some public data available on airport locations. I used data from opendata.socrata.com (latitudes and longitudes) as well as airportcodes.org (city to airport code mapping), which I combined into a file you can see here. I then read this file into a database on my web server.

Django Models

This is my models.py:
from django.db import models
from django.forms import ModelForm

# read the django docs on models: 
# https://docs.djangoproject.com/en/dev/topics/db/models/

# a model for an airport
class Airport(models.Model):
    '''class representing an airport'''

    # airport letter code (usu 3 or 4 letters)
    code = models.CharField('Code', max_length=10)
    # long name (city plus airport name)
    longname = models.CharField('Name', max_length=100)
    # airport latitude in degrees
    latitude = models.DecimalField('Latitude', max_digits=8, decimal_places=5)
    # airport longitude in degrees
    longitude = models.DecimalField('Latitude', max_digits=8, decimal_places=5)

    # methods
    def get_id(self):
        return self.id

    def get_code(self):
        return self.code

    def get_name(self):
        return self.longname

    def get_lat(self):
        return self.latitude

    def get_lon(self):
        return self.longitude
The Airport class is very straightforward. Its attributes are airport code, full airport name, latitude, and longitude. The full name is often a place—e.g., the code JFK corresponds to the name NewYork,NY-Kennedy.

Django URLS

Here's urls.py:
from django.conf.urls import patterns, include, url

# The main page is airapp/test. This is the gateway. 
# If the user successfully enters two airport codes, okay
# Otherwise, he goes to airapp/fail
# the airapp/get_names is used for autocomplete

urlpatterns = patterns('',
    # main view of the app
    url(r'^test/$', 'airapp.views.formview'),
    # this is used for autocomplete
    url(r'^get_names/$', 'airapp.views.getnamesview'),
    # this view is activated when the user input-ed codes are not found
    url(r'^fail/$', 'airapp.views.failview'),
)
There are 3 cases here:
  1. airapp/test
  2. airapp/get_names
  3. airapp/fail
The first is the main page; the second is a special view used to auto-complete the form fields (more on this below); and the third handles the case in which the user inputs airport codes which are not found in the database.

Django Views

Here's my views.py:
from django.shortcuts import render, render_to_response
from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.template.loader import get_template
from django.template import Context 
from django.views.generic.base import TemplateView 
from airapp.models import Airport
from django.core import serializers
import os, re, math
import json
from django import forms

#########################

def km2mile(x):
	'''a function to convert km to mile'''
	return int(x * 0.621371)

def calc_dist(lat1, lon1, lat2, lon2):
	'''a function to calculate the distance in miles between two 
	points on the earth, given their latitudes and longitudes in degrees'''

	# LAX
	#lat2 = math.radians(33.9425)
	#lon2 = math.radians(118.4072)
	# JFK
	#lat1 = math.radians(40.6397)
	#lon1 = math.radians(73.7789)

	# jfk -> lax is 2467 miles

	# covert degrees to radians
	lat1 = math.radians(lat1)
	lon1 = math.radians(lon1)
	lat2 = math.radians(lat2)
	lon2 = math.radians(lon2)

	# get the differences
	delta_lat = lat2 - lat1
	delta_lon = lon2 - lon1

 	# print(delta_lat)
 	# print(delta_lon)

	# Haversine formula, 
	# from http://www.movable-type.co.uk/scripts/latlong.html
	a = ((math.sin(delta_lat/2))**2) + math.cos(lat1)*math.cos(lat2)*((math.sin(delta_lon/2))**2) 
	c = 2 * math.atan2(a**0.5, (1-a)**0.5)
	# earth's radius in km
	earth_radius = 6371 
	# return distance in miles
	return km2mile(earth_radius * c)

class MyForm(forms.Form):
	'''A class for a form with two airport codes'''
	code1 = forms.CharField(max_length=50)
	code2 = forms.CharField(max_length=50)

def formview(request):
	'''A view for the main form in which the user must enter two airport codes - 
	see https://docs.djangoproject.com/en/dev/topics/forms/'''

	# If the form has been submitted...
	if request.method == 'POST': 

		# A form bound to the POST data that has fields for user name and user password
		form = MyForm(request.POST) 

		# All validation rules pass
		if form.is_valid(): 

			# first airport code
			code1 = form.cleaned_data['code1']
			# second airport code
			code2 = form.cleaned_data['code2']

			# check that codes exists in database
			if ( Airport.objects.filter(code=code1).exists() and \
			Airport.objects.filter(code=code2).exists()):

				# calculate dist bet the airports from their latitudes and longitudes
				mylat1 = Airport.objects.get(code=code1).get_lat()
				mylon1 = Airport.objects.get(code=code1).get_lon()
				mylat2 = Airport.objects.get(code=code2).get_lat()
				mylon2 = Airport.objects.get(code=code2).get_lon()
				mydist = calc_dist(mylat1, mylon1, mylat2, mylon2)

				# render out.html, a page telling the user the distance
				return render(request, 'airapp/out.html', {'distance':mydist})

			# if not, go to "fail" page
			else:
				# Redirect to fail page after POST
				return HttpResponseRedirect('/airapp/fail/')

	else:
		# An unbound form
		form = MyForm()

	# pass variables: form
	return render(request, 'airapp/formtemplate.html', {'form': form})

def failview(request):
	'''A view to send user to the fail page if he enters the wrong airport codes'''

	return render(request, 'airapp/fail.html')

def getnamesview(request):
	'''This view is for autocompleting - see tutorial at 
	http://flaviusim.com/blog/AJAX-Autocomplete-Search-with-Django-and-jQuery/'''

	if request.is_ajax():
		q = request.GET.get('term', '')
		airports = Airport.objects.filter(longname__icontains = q )[:20]
		results = []
		for airport in airports:
			airport_json = {}
			airport_json['id'] = airport.code
			airport_json['label'] = airport.longname
			airport_json['value'] = airport.code
			results.append(airport_json)
		data = json.dumps(results)
	else:
		data = 'fail'
	mimetype = 'application/json'
	return HttpResponse(data, mimetype)
The first two functions, km2mile and calc_dist, could be in any ordinary (non-Django) python program. Their purpose is to return the distance between two locations, given the latitudes and longitudes of those points. I lifted the distance formula for this calculation from this blog. Much of the rest of the code is modelled after the discussion of forms in the Django docs. The auto-complete part, in my getnamesview, is a bit tricky and is copied nearly verbatim from this tutorial: AJAX Autocomplete Search With Django and jQuery. As we can see, this is exercise is mostly about tying together three distinct strands: a distance calculation, Django forms stuff, and auto-complete. None of them are too hard on their own—we simply have to mush them into one functional piece.

Django Templates

Here's the main template, airapp/formtemplate.html:
{% block titleblock %}

<title>Oliver | Airapp</title> 

<link rel="stylesheet" href="http://code.jquery.com/ui/1.8.18/themes/base/jquery-ui.css" 
 type="text/css" media="all" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" 
 type="text/javascript">
</script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" 
 type="text/javascript"></script>

<!-- Proper style would be to put this stuff in its own .js file, but I'm going to be lazy -->
<script> 
	$(document).ready(function() {
		console.log("Testing ...");

		$(function() {
		  $("#id_code1").autocomplete({
		    source: "/airapp/get_names/",
		    minLength: 2,
		  });
		});

		$(function() {
		  $("#id_code2").autocomplete({
		    source: "/airapp/get_names/",
		    minLength: 2,
		  });
		});

	}); 
</script>

{% endblock %}

{% block sidebar %} 

	<b><a href='/'>HOME</a></b>
	<br>
	<br>
	<b><a href='/article/all/'>Sitemap</a></b>
	<br>
	<br>

{% endblock %}  

{% block content %}

<div class="text2">

	<h2>Airport App <i>(Testing 1, 2, 3 ...)</i></h2>
	<br>

	<h3>
	To find the distance in miles between two airports, 
	enter two U.S. cities below and select the airport codes 
	from the lists that appear.
	</h3>

	Example entries:
	<ul>
	<li>NewYork,NY-Kennedy <b>JFK</b></li>
	<li>LosAngeles,CA <b>LAX</b></li>
	<li>Chicago,IL-O'Hare <b>ORD</b></li>
	</ul>

	<br>

	<form action="/airapp/test/" method="post">{% csrf_token %}

		<p>
		<div class="ui-widget">
		<label for="id_code1">Code1:</label>
		<input id="id_code1" type="text" name="code1" maxlength="100" />
		</div>
		</p>

		<p>
		<div class="ui-widget">
		<label for="id_code2">Code2:</label>
		<input id="id_code2" type="text" name="code2" maxlength="100" />
		</div>
		</p>

		<input type="submit" value="Submit" />

	</form>

	<br>
	<br>

</div>

{% endblock %}

{% block footer %}
<small>© 2014 Oliver</small>
  
{% endblock %}
Here's airapp/out.html, which displays the output distance after the airport codes have been successfully entered into the form:
{% extends "base.html" %}

{% block titleblock %}
<title>Oliver | Airapp</title> 
{% endblock %}

{% block sidebar %} 

<div class="text2">

	<b><a href='/'>HOME</a></b>
	<br>
	<br>
	<b><a href='/article/all/'>Sitemap</a></b>
	<br>
	<br>
</div>

{% endblock %}  

{% block content %}

<div class="text2">

	<br> <br>
	<h2><i>(:-D)</i></h2>
	<br>
	<br>

	<h1>
	The distance between these two airports is <u>{{distance}} miles</u>.
	</h1>

	<br>

	<b><a href="/airapp/test/">back</a></b>

</div>

{% endblock %}

{% block footer %}
<small>© 2014 Oliver</small>
  
{% endblock %}
Here's the template airapp/fail.html, which is where the user lands if the airport codes are not in the database:
{% extends "base.html" %}

{% block titleblock %}
<title>Oliver | Airapp</title> 
{% endblock %}

{% block sidebar %} 

<div class="text2">

	<b><a href='/'>HOME</a></b>
	<br>
	<br>
	<b><a href='/article/all/'>Sitemap</a></b>
	<br>
	<br>
</div>

{% endblock %}  

{% block content %}

<div class="text2">

	<br> <br>
	<h2>
	<i>Oh no!</i>
	<br>
	<br>
	Those airports were not found in the database.
	</h2>
	<br>

	<b><a href="/airapp/test/">back</a></b>

</div>

{% endblock %}

{% block footer %}
<small>© 2014 Oliver</small>
  
{% endblock %}

The Finished Product

Here's the finished app, in all of its admittedly moderate glory :)
Advertising

image


image


image