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. Airportsby Oliver; 2014
The Problem
This article is no longer up to date or maintained
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.longitudeThe 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:
- airapp/test
- airapp/get_names
- airapp/fail
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 # 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 (you can scroll in this block):{% 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
The finished app, in all of its admittedly moderate glory, has since been retired, leaving this screenshot as the only trace of its existence: