London Buses and The Javascript Geolocation API

The wonderful people at Transport For London (TFL) recently released (but didn’t seem to publicise) a new page on their site that would give you a countdown listing of buses due to arrive at any given stop in London.

This is the physical one (which only appears on some bus stops):

And this is the website one, as found at countdown.tfl.gov.uk

countdown

Before I continue with the technical blithering, I’d like quantify how useful this information is by way of a use case: you’re in a pub/bar/club, a little worse for wear, the tubes have stopped running, no cash for a cab, it’s raining, no jacket. You can see a bus stop from a window, but you’ve no idea how long you’d have to wait in the rain before your cheap ride home arrived. IF ONLY this information were freely available online so you can check if you have time for another drink/comfort break/say your goodbyes before a short stroll to hail the arriving transport.

With this in mind I decided to create a mobile friendly version of the page.

If you visit the tfl site (above) and fire up fiddler you can see that the request for stops near you hits one webservice which returns json data,

fiddler_tfl_countdown_1

and then when you select a stop there’s another call to another endpoint which returns json data for the buses due at that stop:

fiddler_tfl_countdown_2

Seems easy enough. However, the structure of the requests which follow on from a search for, say, the postcode starting with “W6” is a bit tricky:

http://countdown.tfl.gov.uk/markers/swLat/51.481382896100975/swLng/-0.263671875/neLat/51.50874245880333/neLng/-0.2197265625/?_dc=1315778608026

That doesn’t say something easy like “the postcode W6”, does it? It says “these exact coordinates on the planet Earth”.

So how do I emulate that? Enter JAVASCRIPT’S NAVIGATOR.GEOLOCATION!

Have you ever visited a page or opened an app on your phone and saw a popup asking for your permission to share your location with the page/app? Something like:

Or in your browser:

image

This is quite possibly the app attempting to utilise the javascript geolocation API in order to try and work out your latitude and longitudinal position.

This information can be easily accessed by browsers which support the javascript navigator.geolocation API. Even though the API spec is only a year old, diveintohtml5 point out it’s actually currently supported on quite a few browsers, including the main mobile ones.

The lat and long can be gleaned from the method

navigator.geolocation.getCurrentPosition

which just takes a callback function as a parameter passing a “position” object e.g.

navigator.geolocation.getCurrentPosition(showMap);

function show_map(position) {
      var latitude = position.coords.latitude;
      var longitude = position.coords.longitude;
      // let's show a map or do something interesting!
}

Using something similar to this we can pad the single position to create a small area instead, which we pass to the first endpoint, retrieve a listing of bus stops within that area, allow the user to select one, pass that stop ID as a parameter to the second endpoint to retrieve a list of the buses due at that stop, and display them to the user.

My implementation is:

$(document).ready(function() {
	// get lat long
	if (navigator.geolocation){
		navigator.geolocation.getCurrentPosition(function (position) {
			getStopListingForLocation(position.coords.latitude, position.coords.longitude);
		});
	} else {
		alert('could not get your location');
	}
});

Where getStopListingForLocation is just

function getStopListingForLocation(lat, lng){
	var swLat, swLng, neLat, neLng;
	swLat = lat - 0.01;
	swLng = lng - 0.01;
	neLat = lat + 0.01;
	neLng = lng + 0.01;

	var endpoint = 'http://countdown.tfl.gov.uk/markers/swLat/' + swLat + '/swLng/' + swLng + '/neLat/' + neLat + '/neLng/' + neLng + '/';

	$.ajax({
				type: 'POST',
				url: 'Proxy.asmx/getMeTheDataFrom',
				data: "{'here':'"+endpoint+"'}",
				contentType: "application/json; charset=utf-8",
				dataType: "json",
				success: function(data) { displayStopListing(data.d); }
			});
}

The only bit that had me confused for a while was forgetting that browsers don’t like cross browser ajax requests. The data will be returned and is visible in fiddler, but the javascript (or jQuery in my case) will give a very helpful “error” error.

As such, I created the World’s Simplest Proxy:

[System.Web.Script.Services.ScriptService]
public class Proxy: System.Web.Services.WebService
{

    [WebMethod]
    public string getMeTheDataFrom(string here)
    {
        using (var response = new System.Net.WebClient())
        {
            return response.DownloadString(here);
        }
    }
}

All this does, quite obviously, is to forward a request and pass back the response, running on the server – where cross domain requests are just peachy.

Then I have a function to render the json response

function displayStopListing(stopListingData){
var data = $.parseJSON(stopListingData);
	$.each(data.markers, function(i,item){
      $("<li/>")
	  .text(item.name + ' (stop ' + item.stopIndicator + ') to ' + item.towards)
	  .attr("onclick", "getBusListingForStop(" + item.id + ")")
	  .attr("class", "stopListing")
	  .attr("id", item.id)	  
	  .appendTo("#stopListing");
    });	
}

And then retrieve and display the bus listing

function getBusListingForStop(stopId){
var endpoint = 'http://countdown.tfl.gov.uk/stopBoard/' + stopId + '/';
	
	$("#" + stopId).attr("onclick","");
 
	$.ajax({
		type: 'POST',
		url: 'Proxy.asmx/getMeTheDataFrom',
		data: "{'here':'"+endpoint+"'}",
		contentType: "application/json; charset=utf-8",
		dataType: "json",
		success: function(data) { displayBusListing(data.d, stopId); }
	});
}
 
function displayBusListing(busListingData, stopId){
	var data = $.parseJSON(busListingData);
	
  $("<h2 />").text("Buses Due").appendTo("#" + stopId);
	  
	$.each(data.arrivals, function(i,item){
	  
      $("<span/>")
	  .text(item.estimatedWait)
	  .attr("class", "busListing time")
	  .appendTo("#" + stopId);
 
      $("<span/>")
	  .text(item.routeName + ' to ' + item.destination)
	  .attr("class", "busListing info")
	  .appendTo("#" + stopId);
 
      $("<br/>")
	  .appendTo("#" + stopId);
    });	
}

(yes, my jQuery is pants. I’m working on it..)

These just need some very basic HTML to hold the output

<h1>Bus Stops Near You (tap one)</h1> 
<ul id="stopListing"></ul> 

Which ends up looking like

The resultingfull HTML can be found here, the Most Basic Proxy Ever is basically listed above, but also in “full” here. If you want to see this in action head over to rposbo.apphb.com.

Next up – how this little page was pushed into the cloud in a few seconds with the wonder of AppHarbor and git.

UPDATE

Since creation of this “app” TFL have created a very nice mobile version of their own which is much nicer than my attempt! Bookmark it at m.countdown.tfl.gov.uk :


6 thoughts on “London Buses and The Javascript Geolocation API

  1. Awesome find thank you very much needed this exact thing to find the nearest; it’s a shame that the return is so ridiculously huge.

    To combat this I have created a small bit of jQuery to order the bus stops in order of distance for the Geolocation of the user (if you would like to look at the code feel free to give us a shout!)

  2. Hey,

    Thanks for the blog post was really useful for a project I am working on myself which needed this information from TFL as well.

    I extended what you have done to work with Postcodes as well retrieving the most local bus-stops for that postcode, ordering by distance and displaying using Google Maps.

    While wrapped with jQuery Mobile check it out and if you’d like to know any of what I did like the ordering feel free to email.

Leave a Reply

Your email address will not be published. Required fields are marked *