Getting Browser Location in Chrome and Deprecating Powerful Features on Insecure Origins

A while ago I created the most useful web page I’ve ever written; an extremely basic page to list the bus arrival times, based on your current location anywhere in London!

I have used this page several times a day on an almost daily basis since I created it, as does my wife; every morning we need to know when the next bus will arrive near our house in order to know when to rush out with our children to take them to school.

I used it every morning to check if I needed to rush out the door to catch the rare “express” bus that would get me to the tube much quicker than the usual one.

I discovered just how useful it is whilst in a pub, deciding whether I had time for another drink before heading home; a quick glance at my phone and I could find out that the next bus home wasn’t for 20 minutes – plenty of time!

However, recently it stopped working for me. It still worked just fine on my wife’s iPhone (and hence Safari), but not on my Nexus (Chrome). It works on my laptop too, or at least appeared to.

So what’s going on?

Continue reading

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 :