Now that we have created our models and our views, we can continue to implement the front end of the application. For that, we will simply create one HTML page, which will contain a form for uploading files, a list of the uploaded routes and a map container. We will take advantage of the Django template system for this.
Listing 7. Sample HTML code
<!-- Leaflet CSS and JS files -->
<link rel=”stylesheet” href=”/static/leaflet/leaflet.css”/>
<form id=”form” method=”post” enctype=”multipart/form-data”>{% csrf_token %}
<legend><h2>Upload GPX file</h2></legend>
<li id={{ route.pk }} class=”route-link”>{{ route.name }}</li>
{% endfor %}
The body of the HTML file can be divided into four pieces. The first is the form which will allow the users to upload the files. The second is a container for the list of routes in the database. The third is an empty container, which will be filled via AJAX with some data about the route the user is visualizing. The fourth container is initially empty, but will contain the map once the page is loaded.
In order to use Leaflet.js, we have to download some JavaScript and some CSS file which have to be included in the document. This files can be downloaded from the Leaflet homepage: http://leafletjs.com/
download.html. Once they are downloaded, we only have to include them in the static files directory and load them as regular JavaScript and CSS files. However, we have to be careful with two details; first of all, Leaflet need jQuery to work, so we have to download it (from http://jquery.com/download/) and include it in the document before the Leaflet scripts. Second, we will create a script to initialize the map, which has to be executed strictly after the container for the map is loaded, for this we can simply include the script in the body of the document, below the map container.
As mentioned, we will load the details of each route via AJAX, so we will need to create another view which will return a JSON object containing the details of the route. We could also return an XML document, however, since we have to embed a GeoJSON object in it and we will parse it in JavaScript, it seems more adequate to use a JSON.
Listing 8. Our new view
def routeJSON(request, pk):
route = Route.objects.get(pk=pk) if route is not None:
rt = {‘name’:route.name, ‘dist’:route.length(),
‘nearest’:route.nearest().name}
rt[“geojson”] = json.loads(route.geoJSON())
return HttpResponse(json.dumps(rt),
content_type=”application/json”)
return HttpResponse(“”, content_type=”application/json”)
Note that we load the GeoJSON string into a Python object before dumping it again. This seems redundant, however it is necessary, for if we dump a JSON string, we will have issues with characters like the quotes.
Once all this is ready, we can follow to create our map. We will create a file called map.js in the static files directory, which will contain the script initializing the map and the functions that allow the asynchronous loading the routes. First we will take care of creating the map, the code needed is the following.
Listing 9. The sample code
var route;
var map = L.map(‘map’);
var osmLayer = L.tileLayer(‘http://{s}.tile.openstreetmap.org/{z}/{x}/ {y}.png’);
map.addLayer(osmLayer);
map.fitWorld();
First, we declare a variable called route, which will later contain the route the user is currently viewing.
Next, we call the map() function from the Leaflet library, which receives an identifier and creates a map on the container with that id, we store it on a variable so that we can manipulate it later.
Leaflet works mainly with layers; markers, lines, tiles, etc. are all layers, which can be added and remove to the map. In order to be able to actually see something, we have to include at least one tile layer, which is in charge of rendering the map. There are several free tile providers, but for this example we will be using the ones provided by OpenStreetMaps, though we can add several tile layers at the same time and allow he user to switch among them at will.
Note
You can find a script which creates short cuts for several popular tile providers in the following URL: https://
gist.github.com/mourner/1804938.
After we have created all the tile layers we wish we just have to add them to the map, with the addLayer()
function on the map or with the addTo() function of the layers. Finally, it is recommended to set view port of the map to something, since it will show nothing if it has no view port. An easy way to do this when developing is the fitWorld() function of the map.
Finally, we have add an event to each element on the list so that when the user click on it, a route is loaded and the details of the route are displayed
Listing 10. A route
$(‘.route-link’).click(function(){
var id = $(this).attr(‘id’);
$.getJSON(‘/ajax/’+id+’/route’, function(data) {
//Remove the previous route and add the new one if(route!=null){
map.removeLayer(route);
}
route = L.geoJson(data.geojson);
route.addTo(map);
map.fitBounds(route.getBounds());
//Add the data to the data panel
$(‘#data’).html(‘<h2>’+data.name+’</h2><p><b>Distance:
</b>’+data.dist +’</p><p><b>Nearest: </
b>’+data.nearest+’</p>’);
});
});
When the user clicks on one of the “links”, a AJAX call is made to an URL which returns the details of the route. The first thing to do, is to remove the route which is currently being displayed, if not, we can end up with a mess of lines in the map. Then, we just create a layer from the GeoJSON object, we add it to the map and we set the view port of the map to the bound of the route.
Here, we have transparently created a geometric object and added it to the map, however, Leaflet provides some classes to represent polygons, LineString and other geometric objects in a similar way to GeoDjango (but much more primitive). Though I wont explain all the functions on the library, I encourage anyone interested to explore the leaflet API (on http://leafletjs.com/reference.html) which gives a comprehensive guide to using and extending leaflet.
Finally, we just have to add the rest of the data downloaded to the document and it is finished.