Posts tagged ‘HTML5’

April 19, 2011

Bing Maps and HTML5 Audio – Once more, with feeling!

I’ve already written articles demonstrating a couple of new HTML5 features and Bing Maps – notably, the <canvas> element and the geolocation API. In this post, I’ll have a look at one of the other new elements in the HTML5 specification, the <audio> tag.

Personally, I find webpages that play music in the background are right up there on the “annoying” list next to those that use the <blink> tag, or those riddled with popup banner adverts. However, there are some genuine use cases for audio in webpages:

  • Discrete use of subtle sound effects can provide positive feedback for user interfaces. For example, consider touch keyboard inputs that emulate the “clicking” noise of a typewriter to confirm when a key has been pressed.
  • Sound effects can also improve accessibility of a page, by providing audio cues for the elements of a user interface, in much the same way that a screenreader does.

I’m not an expert in UI design or in accessibility, so I’m not claiming that this article exhibits best practice in either of those areas, but at least I’d like to demonstrate what is technically possible using HTML5 <audio> and Bing Maps, by playing a simple sound effect when the mouse is placed over a polygon on the map.

Testing for Browser Audio Support

The HTML5 specification is still not fixed, and the level of support for HTML5 across different browsers is variable. At the time of writing, the most recent version of all major browsers support the <audio> tag itself, although the actual formats of audio file that can be played varies between browser. Rather frustratingly, there is no single file type supported across all browsers, as shown in the following table:

Format IE9 Firefox 3.6 /4 Opera 10.6 Chrome 10 Safari 3
Ogg Vorbis No Yes Yes Yes No
MP3 Yes No No No Yes
Wav Yes Yes Yes No Yes

(information aggregated from http://html5doctor.com/native-audio-in-the-browser/ and  http://thebrowsereview.com/html5/html5-audio-tag-and-format-support/)

Before using any of the new <audio> features, the first thing to do is therefore to check whether the user’s browser supports audio. This can be done in javascript by attempting to create a new audio element and calling the canPlayType function.

var supportsAudio = !!(document.createElement('audio').canPlayType);

This function will return a boolean result of true if the browser supports HTML5 audio, or false otherwise. If the browser does support audio, you can find out if a particular audio file format is supported by passing a parameter to canPlayType, representing the MIME type of an audio format. For example, to test whether the browser supports MP3 audio:

var supportsMP3 = (document.createElement('audio').canPlayType('audio/mpeg');

Note that, unlike the boolean response obtained when canPlayType is called with no parameters (which tests whether the browser implements audio at all), the response given when used to test whether a particular audio file type is supported is either “”, “maybe”, or “probably”.

Specifying a Source Audio File

Having determined the audio formats supported by the browser, you can then use a conditional statement to specify which source file should be used. For example:

var audio = document.createElement('audio');

// Create the source audio file element
var source = document.createElement('source');
// Test if the browser supports MPEG audio
if (audio.canPlayType('audio/mpeg') != "") {
source.type = 'audio/mpeg';
source.src = 'soundfile.mp3';
}
// Test if the browser supports OGG audio
else if (audio.canPlayType('audio/ogg; codecs="vorbis"') != "") {
source.type = 'audio/ogg';
source.src = 'soundfile.ogg';
}

audio.appendChild(source);

If Music Be the Food of Love, Play On…. (Or stop, as appropriate)

The final step is to attach handlers to the map to listen for particular events, and then set these handlers to start or stop the audio as appropriate. For this example, I’m going to attach a handler to the mouseover event of a polygon, causing the audio file to play while the user hovers their cursor over the shape in question:

var Handler = Microsoft.Maps.Events.addHandler(polygon, 'mouseover', function(e) {
audio.play();
});

And, when the user moves their mouse off the polygon, I want to stop the sound again. Note that the HTML5 audio standard doesn’t actually define a stop method, but rather a pause method to stop playback:

var Handler = Microsoft.Maps.Events.addHandler(polygon, 'mouseout', function(e) {
audio.pause();
});

Putting It All Together

A full code listing demonstrating the audio functionality is shown following. This example has been tested and confirmed to work in Firefox 4, Internet Explorer 9 and Chrome

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Bing Maps HTML5 Audio</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"></script>
<script type="text/javascript">

// Declare a shorthand alias for the Microsoft.Maps namespace
var MM = Microsoft.Maps;

function GetMap() {
// Create a basic map
var map = new MM.Map(document.getElementById("mapDiv"),
{ credentials: "YOUR BING MAPS KEY HERE",
center: new Microsoft.Maps.Location(54, -2),
mapTypeId: Microsoft.Maps.MapTypeId.birdseye,
zoom: 5
});

// Create a polygon
var polygon = new MM.Polygon([new MM.Location(50, -7),new MM.Location(52.5, 2),new MM.Location(59, -3),new MM.Location(57, -6),new MM.Location(50, -7)]);
// Add the polygon to the map
map.entities.push(polygon);

// Try to create an audio element
var audio = GetAudio('notify');
// If audio is supported
if (audio) {
// Play music when the mouse is over the polygon
var Handler = Microsoft.Maps.Events.addHandler(polygon, 'mouseover', function(e) {
audio.play();
});
// Stop audio when the mouse leaves the polygon
var MouseOutHandler = Microsoft.Maps.Events.addHandler(polygon, 'mouseout', function(e) {
audio.pause();
});
}
}

/* Tests for browser audio support, and specifies
* the appropriate source file extension depending
* on supported audio file types */
function GetAudio(filename) {
// Try creating an <audio> tag
var audio = document.createElement('audio');
// If the browser supports <audio>
if (audio.canPlayType) {
// Create the source audio file element
var source = document.createElement('source');
// Test if the browser supports MPEG audio
if (audio.canPlayType('audio/mpeg') != "") {
source.type = 'audio/mpeg';
source.src = filename + '.mp3';
}
// Test if the browser supports WAV audio
else if (audio.canPlayType('audio/wav') != "") {
source.type = 'audio/wav';
source.src = filename + '.wav';
}
// Test if the browser supports OGG audio
else if (audio.canPlayType('audio/ogg; codecs=vorbis') != "") {
source.type = 'audio/ogg; codecs=vorbis';
source.src = filename + '.ogg';
}
// Append the matching format file to the <audio> element
audio.appendChild(source);

return audio;
}
// If <audio> is not supported
else {
return null;
}
}
</script>
</head>
<body onload="GetMap();">
<div id='mapDiv' style="position:relative; width:600px; height:800px;"></div>
</body>
</html>

If you can imagine audio in a screenshot, then it probably looks a bit like this:

image

And you an see a live demo by going to: http://a3uk.com/clients/bingmaps/v7/html5audio/html5audio.htm

April 15, 2011

Bing Maps AJAX v7 HeatMap Library

A few months ago, I posted an article demonstrating some work I’d done creating heatmaps of crime data using the Bing Maps v7 AJAX control. At that time, my intention had originally been to package up my heatmap code and put it up on codeplex. However, I’m not sure that I can be bothered now to create a brand new codeplex project for what is essentially a single 6kb 150-line javascript file, so I’ve decided to just upload it here instead. If anybody wants to contribute improvements or additions to the code, please feel free to add them to the comments here and I’ll create new versions as I feel like it.

It’s completely unsupported, comes with no warranties whatsoever, but you’re free to use it in any projects you like. All I ask is that, if you find it helpful, please let me know (commenting on this post will do).

You can download the library itself, some sample data, and a few HTML pages illustrating different sample usage in this zip archive. Note that, since the heatmap is created using a <canvas> element, it is only supported by browsers than support HTML5 (IE9, Firefox 3.5+, Chrome, etc.).

Basic Usage

To add a new heatmap layer to a map, use the syntax as follows:

HeatMapLayer( map, locations );

  • map is the Microsoft.Maps.Map object on which to overlay the heatmap
  • locations is an array of Microsoft.Maps.Location elements

The code archive includes a set of data from Norfolk Police constabulary, which is shown below:

image

Advanced Usage

The constructor method also accepts an optional parameter in which you can specify the radius and intensity of each heat spot, as well as the colour gradient that should be applied. This is demonstrated in the following listing:

HeatMapLayer( map, locations,
{ intensity: 0.25,
radius: 25,
colourgradient: {
0.0: ‘green’,
0.5: ‘yellow’,
1.0: ‘red’
}
});

This specifies that each heat spot should have a radius of 25 pixels, with a central opacity of 0.25. The temperature gradient is such that the hottest areas appear red, the coolest areas are green, with a mid-gradient of yellow. When applied to a set of data showing the location of Morrisons supermarkets in the UK, this creates the following heatmap:

image

Compare this to a heatmap based on the same settings plotting the location of Waitrose stores (for anybody reading this from outside the UK, Waitrose is a posh, southern supermarket!). Pretty effective comparison, no?:

image

February 23, 2011

Heat Mapping Crime Data with Bing Maps and HTML5 Canvas

Crime data is frequently presented using “heat maps”. See, for example:

imagehttp://www.bbc.co.uk/truthaboutcrime/crimemap/ image http://www.zubedpi.com/

One of the reasons why heat maps are used to visualise crime data is that crimes are typically recorded as individual incidents, occurring at one particular location, yet we tend to want to think of areas at high- or low- risk of crime. We therefore need some way of aggregating those individual crime occurrences into regions that can be analysed.

It doesn’t necessarily make sense to create a thematic map of crimes based on geographic or administrative divisions – a burglar probably does not have much regard for whether he commits a crime in one polling district/ward/street of a city or another, for example, and we should not attempt to fit the distribution of crimes to such arbitrary regions. A heatmap creates a way of summarising and presenting a set of point data with the only influencing factor being the underlying geographic distribution of the points – a greater number of points in the same proximity leads to a “hotter” region.

Seeing as the government has just launched the http://www.police.uk/data portal providing access to a dataset of all reported crime in England and Wales (at a ridiculous cost of £300,000 for 40 basic CSV files, updated monthly), I thought that this would be a good opportunity to update my old heatmapping code to use Bing Maps AJAX v7. What’s more, seeing as Internet Explorer 9 now supports the <canvas> element, I thought I’d try creating heat maps dynamically in the client-side browser rather than rendering them as images on the server-side, as I typically have done in the past.

To start with, I created an array of locations from which my heatmap would be created. I used all of the “anti-social behaviour” crimes reported by Norfolk constabulary during December 2010 (3,185 records). Having converted each record in the CSV file to a Microsoft.Maps.Location, my array looked like this:

var locations = [
new Microsoft.Maps.Location(52.672802972383, 0.943923796417529),
new Microsoft.Maps.Location(52.624236498992, 1.29493956651563),
new Microsoft.Maps.Location(52.6218783667792, 1.29080178760268),
…
]

I then created a <canvas> element in my HTML page and placed it so that it exactly covered the <div> element in which my Bing map would be created:

var canvas = document.createElement('canvas');
canvas.id = 'heatmapcanvas'
canvas.style.position = 'absolute';
canvas.height = 800;
canvas.width = 1024;
canvas.style.zIndex = 1;
document.getElementById('mapDiv').lastChild.appendChild(canvas);

Note that, as Bing Maps v7 doesn’t have an AddCustomLayer() method as in the previous v6.x versions, I’m having to manually add the canvas element into the correct place in the DOM using appendChild(). This method is not perfect as it places the canvas on top of the map controls as well as the map tiles – I’m going to be looking at this issue later.

The next step is to loop through each of these locations and add a radial gradient at the appropriate pixel location on the canvas (which, because the canvas is placed exactly over the map, can be determined by the tryLocationToPixel() method). The appearance of each heat point produced is affected by two parameters – radius determines the extent to which heat spreads out around a point, and intensity determines how hot it is at the centre of each point.

// Create the Intensity Map
for (var i = 0; i < locations.length; i++) {
var loc = locations[i];

// Convert lat/long to pixel location
var pixloc = map.tryLocationToPixel(loc, Microsoft.Maps.PixelReference.control);
var x = pixloc.x;
var y = pixloc.y;

// Create radial gradient centred on this point
var grd = ctx.createRadialGradient(x, y, 0, x, y, radius);
grd.addColorStop(0.0, 'rgba(0, 0, 0, ' + intensity + ')');
grd.addColorStop(1.0, 'transparent');

// Draw the heatpoint onto the canvas
ctx.fillStyle = grd;
ctx.fillRect(x - radius, y - radius, x + radius, y + radius);
}

To start with, I used values of radius 20 and intensity 0.25. This meant that the centre of any single point would be plotted as black colour with 0.25 transparency. The visibility of each point would gradually diminish to be fully transparent 20 (pixels) away from the centre. However, the heat of any points closer than 20 (pixels) to each other would create an additive effect, and produce a heat intensity map like this:

image

Having created a single-channel heat intensity map as above, you can then colourise it by creating a linear gradient, and mapping the alpha values of each pixel on the canvas to the appropriate value from the gradient.

The following code demonstrates a linear colour gradient that progresses from cool colours to hot colours, as typically used in a heat map:

function createColourGradient() {
var ctx = document.createElement('canvas').getContext('2d');
var grd = ctx.createLinearGradient(0, 0, 256, 0);
grd.addColorStop(0.0, 'magenta');
grd.addColorStop(0.25, 'blue');
grd.addColorStop(0.5, 'green');
grd.addColorStop(0.75, 'yellow');
grd.addColorStop(1, 'red');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 256, 1);
return ctx.getImageData(0, 0, 256, 1).data;
}

And this colour gradient can be used to colour the intensity map as follows:

      var dat = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pix = dat.data;
var ColourGradient = createColourGradient();
for (var p = 0; p < pix.length;) {
var a = pix[p + 3]*4; // get the alpha value of this pixel
pix[p] = ColourGradient[a]; // get the red value from linear gradient that corresponds to this alpha
pix[p + 1] = ColourGradient[a + 1]; //get the green value based on alpha
pix[p + 2] = ColourGradient[a + 2]; //get the blue value based on alpha
p+=4; // Move on to the next pixel
}
ctx.putImageData(dat, 0, 0);

// Populate the canvas
ctx.fillRect(0, 0, 1024, 800);

The preceding code might need a bit of explanation…

  • Each pixel in the array returned by getImageData() is represented by 4 bytes of data, those bytes representing the Red, Green, Blue, and Alpha values of that pixel.
  • So, in the for loop above, the line pix[p+3]*4 is used to return the alpha value only of each pixel in the pix array. (Remember that the canvas was created by placing radialgradients around each point, which varied from an alpha of intensity at the centre to alpha of 0 at a distance of radius from the centre).
  • I then look up the pixel that lies at the equivalent position in the ColourGradient spectrum, and copy the first 3 bytes from it into the pixel array, replacing the purely black image with the Red, Green, and Blue values.

The result of colourising the map using the linear gradient defined above is as follows:

image

Of course, HTML5 makes it very easy to colour the heat map using any colour gradient you want. Changing the ColorStops in the linear gradient as follows, for example:

grd.addColorStop(0.0, 'black');
grd.addColorStop(0.5, 'red');
grd.addColorStop(0.9, 'pink');
grd.addColorStop(1, 'white');

leads to the following:

image

There’s still a few issues I’m having with my code – namely trying to get the <canvas> element inserted at the right place in the DOM to be above the map tiles but underneath the navbar (it seems that all elements in the AJAX v7.0 control are displayed at z-index:0, so I can’t use this as a method top position it). Also, whilst the code above works great in IE9 and Google Chrome, Firefox often gives me the “slow script” warning… I’m not sure what element it’s unhappy with, or whether it’s just that Firefox is not as HTML5-compliant as it claims to be… (ahh the joys of creating cross-browser web apps!)

If I get these issues ironed out, and if anybody shows any interest, then I might package up the complete heatmap code into a reusable class and pop it up on codeplex.

February 11, 2011

The W3C Geolocation API, Internet Explorer 9, and Bing Maps

Yesterday, Microsoft released the first release candidate of Internet Explorer 9. Amongst other features, the latest incarnation of Microsoft’s browser now includes support for the W3C Geolocation API. What this basically means is that (if you allow them to do so), any websites you visit on the internet will be able to determine your current real-world location as you are browsing, and potentially update their content accordingly. Location-aware applications are already well-established in the mobile application market (with apps like foursquare and location-enabled twitter clients proving popular) but they are still relatively scarce in the traditional web application domain.

The geolocation API has been around for a while, and I played around with it a bit last summer. Last time I looked at it, I decided it was only of limited practical value, mainly for the following reasons:

  • Firstly, there was very limited support among mainstream desktop browsers.
  • Secondly, for reasons of security, users have to “opt-in” to share their location. This is generally presented as a fairly ugly security warning in the browser, and rather destroys any attempts to seamlessly adapt content for the user’s location.
  • The implementations that did exist seemed buggy or very slow.

With the support for geolocation now added in IE9 (and already present in Firefox, Chrome, and Safari amongst others), at least the first one of these points seems no longer valid. So, I thought it was time to revisit the geolocation API.

A webpage that plots the user’s location on a Bing Map

Fortunately, the Geolocation API is simple and relatively well-documented, so I threw together a quick webpage that displays a Bing Map and (assuming you agree to share your location) centres it on your current location, as reported by your browser.

Here’s the code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd&quot;">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"</a>>
<html xmlns="<a href="http://www.w3.org/1999/xhtml&quot;">http://www.w3.org/1999/xhtml"</a>>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="<a href="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&quot;">http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0"</a>></script>
<script type="text/javascript">
var map = null;
function GetMap() {

// Initialise the map
map = new Microsoft.Maps.Map(document.getElementById("mapDiv"),
{ credentials: "YOURBINGMAPSKEY",
center: new Microsoft.Maps.Location(54, -4),
zoom: 6
});

// Check if the browser supports geolocation
if (navigator.geolocation) {
// Request the user's location
navigator.geolocation.getCurrentPosition(locateSuccess, locateError, { maximumAge: 0, timeout: 30000, enableHighAccuracy: true });
}
else {
window.status = "Geolocation is not supported on this browser.";
}
}

/*
* Handle a succesful geolocation
*/
function locateSuccess(loc) {

var accuracy = loc.coords.accuracy;

// Determine the best zoom level
var zoomLevel = 14;
if (accuracy > 500)
{ zoomLevel = 11; }
else if (accuracy > 100)
{ zoomLevel = 14; }
else if (accuracy <= 100)
{ zoomLevel = 17; }

// Set the user's location
var userLocation = new Microsoft.Maps.Location(loc.coords.latitude, loc.coords.longitude);

// Change the map view
map.setView({ center: userLocation, zoom: zoomLevel });

// Plot a circle to show the area in which the user is located
var locationArea = plotCircle(userLocation, accuracy);
map.entities.push(locationArea);
}

/*
* Handle any errors from the geolocation request
*/
function locateError(geoPositionError) {

switch (geoPositionError.code) {
case 0: // UNKNOWN_ERROR
alert('An unknown error occurred');
break;
case 1: // PERMISSION_DENIED
alert('Permission to use Geolocation API denied');
break;
case 2: // POSITION_UNAVAILABLE
alert('Could not determine location');
break;
case 3: // TIMEOUT
alert('The geolocation request timed out');
break;
default:
}
}

/*
* Plot a circle of a given radius about a point
*/
function plotCircle(loc, radius) {
var R = 6378137;
var lat = (loc.latitude * Math.PI) / 180;
var lon = (loc.longitude * Math.PI) / 180;
var d = parseFloat(radius) / R;
var locs = new Array();
for (x = 0; x <= 360; x++) {
var p = new Microsoft.Maps.Location();
brng = x * Math.PI / 180;
p.latitude = Math.asin(Math.sin(lat) * Math.cos(d) + Math.cos(lat) * Math.sin(d) * Math.cos(brng));
p.longitude = ((lon + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(lat), Math.cos(d) - Math.sin(lat) * Math.sin(p.latitude))) * 180) / Math.PI;
p.latitude = (p.latitude * 180) / Math.PI;
locs.push(p);
}
return new Microsoft.Maps.Polygon(locs, { fillColor: new Microsoft.Maps.Color(128, 0, 0, 255), strokeColor: new Microsoft.Maps.Color(192, 0, 0, 255) });
}
</script>
</head>
<body onload="GetMap();">
<p>Remember to <q>Share Location</q> when prompted to allow your browser to use the geolocation API.</p>
<div id='mapDiv' style="position: relative; width: 640px; height: 480px;"></div>
</body>
</html>

Comparing Browser Results

The results were… interesting. I loaded the page above in three browsers, all running under Windows 7 64-bit:

  • Microsoft IE9 (RC1)
  • Firefox (3.6.13)
  • Google Chrome (9.0.597.98)

Firstly, I tested them on my desktop computer which has a wired connection to my broadband router (this is important!).

Firefox – Wired Connection

Firefox was the first to be tested, and it successfully returned a geolocation result, although the location accuracy was fairly abysmal – placing me somewhere in a circle of radius about 100 miles around London. I’m in Norwich, which is just inside the Northeast extent of the circle, so technically the geolocation was correct within the bounds of its own stated accuracy, but hardly convincing.

firefox_geolocate

Internet Explorer 9 – Wired Connection

IE9 was next, and the result was returned with much greater accuracy, although, as far as I’m concerned, was less correct than the previous result from Firefox. The geolocation result was now shown with an accuracy of about 20 miles around London – a smaller area, although crucially one that no longer included my actual current location!

ie_geolocate

Chrome – Wired Connection

Just when I thought this couldn’t get any worse, I loaded the page in Google Chrome, only to be greeted by a timeout error. I’d set the timeout in my script to 30 seconds, which already seemed way too long for any kind of decent user-experience, but Chrome didn’t even get a result within that time. Oh dear.

chrome_geolocation_timeout

Now, at this point it’s worth thinking about how the geolocation API obtains your current location. On a mobile device, it’s pretty easy – most modern smartphones have inbuilt GPS that give very accurate location readings or, failing that, you can obtain a reasonable estimate from triangulating your location from the set of network towers from which your phone can currently receive signal.

On a computer, the geolocation API relies upon either your IP address, or the known locations of any wireless networks to which you are connected. My broadband provider is a national network, based in London, so if you trace my IP address you’ll likely get the best geolocation match somewhere in London. That explains the IE9 and FF results above. So what if I repeat the test but on my laptop, still connected to my own home router, but this time via wi-fi?

Firefox – Wireless Connection

Oh for Pete’s sake – now Firefox is timing out! Maybe this whole thing was a bad idea. I can barely bring myself to test IE9, it’s bound to be worse…

image

Internet Explorer – Wireless Connection

…oh, but wait! Finally things are looking up! IE9 successfully geolocates in well under a second and , what’s more, the result is a geolocation that is accurate to under 500 metres, and really does include my current location. Finally, this is something that could actually be sensibly used to create location-specific services for me.

image

Chrome – Wireless Connection

Holy smokes! The Chrome result, when used over a wireless network is worthy of note for two reasons. Firstly, it is lightning fast – loading the page and zooming the map in milliseconds. Secondly, that really is where I am right now, accurate to within about 30 metres. And remember I’ve not got any special hardware or anything here – I’m connecting to my own broadband router via my own personal wi-fi. Crikey!

Summary

It’s good to see that IE9 has adopted the geolocation standard (and note that Microsoft really has adopted the standard – I didn’t have to do any browser-specific hacks to make this test work across the three browsers), but I still stand by my previous conclusion of the geolocation standard – it’s just not yet at a point where it can add much practical value.

Even though the implementations are consistent across the three browsers tested here, the results are so wildly different as to make it completely unreliable. To my surprise, Firefox (a browser I have evangelised in the past) was the worst in test – failing to deliver at all over a wireless network, and delivering a next-to-useless result over wired. Chrome delivered a scarily accurate result over wi-fi, but timed out over a wired connection. IE9 was the only browser to actually deliver a result in both cases with anything like reasonable accuracy. However, even then it necessarily is retrieving the location of my IP address in London rather than my actual location, so it’s hard to see how it could add much value at anything other than a national level (for which you may as well lookup the IP address of the client in a geocode database such as http://www.hostip.info/, for which you don’t need to ask the user’s permission)

Follow

Get every new post delivered to your Inbox.

Join 31 other followers