Adding Google's My Location to your Application

A few people have asked me how I get a GPS fix so fast in GL Maps. I wish I could say I had some secret sauce, but I'd be fibbing. The truth is that I'm using the Google Gears Geolocation API. Google Gears is pretty much the new hotness. The Geolocation API determines your geocode by use of several sources:

  • GPS (obviously)
  • WiFi access points
  • Cell Towers IDs
  • Your IP

Generally between the first 3, you can get a pretty accurate location. Unfortunately Google Gears is an ActiveX control that is intended to only be used through JavaScript (it is only accessible through late binding). And though accessing methods through reflection on the PC is feasible, .NET CF does not support late binding with COM. So, that left two possible solutions:

  • Create a C++ DLL that handles all the late binding with COM (very gross) and PInvoke into that
  • Figure out a way to somehow get the result back from JavaScript (hosted in a WebBrowser control)

The second option is rather tricky though; the WebBrowser class does not give you access to the DOM on Windows Mobile. So after banging my head on that for a while; I figured out a crafty way to do it: through the URI of the browser.

The Google Gears Geolocation sample looks as follows:

function successCallback(p) {
var address = p.gearsAddress.city + ', '
+ p.gearsAddress.region + ', '
+ p.gearsAddress.country + ' ('
+ p.latitude + ', '
+ p.longitude + ')';

clearStatus();
addStatus('Your address is: ' + address);
window.location = "http://deadlink?lat=" + p.latitude + "&lon=" + p.longitude;
}

function errorCallback(err) {
var msg = 'Error retrieving your location: ' + err.message;
setError(msg);
}

try {
var geolocation = google.gears.factory.create('beta.geolocation');
geolocation.watchPosition(successCallback, errorCallback, { enableHighAccuracy: false,
gearsRequestAddress: true
});
} catch (e) {
setError('Error using Geolocation API: ' + e.message);
return;
}

Note the change I made on the 9th line (where I set window.location): once we have received the geocode from the asynchronous call, the JavaScript attempts to change the browser's address to a link containing the latitude and longitude in the query string. The WebBrowser class has a Navigating event that fires whenever the URI is changing. This event also gives you the option to look at the URI being navigated to and optionally cancel it through the WebBrowserNavigatingEventArgs parameter.

So, when the Navigating event fires, I cancel it, and grab the latitude and longitude form the URI:


static readonly Regex myLatRegex = new Regex("lat=(.*?)&");
static readonly Regex myLonRegex = new Regex("lon=(.*)");
void myBrowser_Navigating(object sender, System.Windows.Forms.WebBrowserNavigatingEventArgs e)
{
// if the browser tries to navigate, cancel it, and pick up the lat and long from the uri
// it tried to navigate to
e.Cancel = true;
try
{
string url = e.Url.ToString();
Match latMatch = myLatRegex.Match(url);
Match lonMatch = myLonRegex.Match(url);
if (!latMatch.Success || !lonMatch.Success)
return;
Geocode geo = new Geocode();
geo.Latitude = double.Parse(latMatch.Groups[1].Value);
geo.Longitude = double.Parse(lonMatch.Groups[1].Value);
UpdateGeocode(geo);
}
catch (Exception ex)
{
}
}

Obviously this is a hack, but it is one that will not fail and is easy to maintain as the Google Gears API evolves. And it will work on both the PC or Windows Mobile!

2 comments:

Sean said...

Outstanding! I was wondering how to do the same thing the moment Gears came out for WinMo! I figured you could host a browser like you did, but I ran across the same problem with being unable to access the DOM. This is an excellent alternative to writing a huge wrapper around geolocation functionality that I can use in my application Twobile. Thank you so much!

Anonymous said...

Very nice trick!
Thanks