Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Learn How to Find GPS Location on Any SmartPhone, and Then Make it Relevant

0.00/5 (No votes)
10 Feb 2009 1  
A step by step tutorial for getting GPS from any SmartPhone, even without GPS built in, and then making location useful.

Contents

Introduction

Some time ago, I stated in another article that I'd take the idea of location broadcasting and develop a mobile solution as a follow-up. The problem back then was I had no means to get location data off of a cell phone, or a way to make it useful. My, how times have changed since then! In this article, I'll demonstrate how to get your phone's GPS coordinates…

Even if your phone doesn't have GPS built in (like mine).

Why is Location Hard?

Phone technology is maturing at an alarming rate. A large number of current generation phones have GPS built in. Nonetheless, “smart” phones have an average price tag in the hundreds, and most of us haven't rushed out to buy a replacement for our current phone (especially with the troubled economy as of this writing).

The fact that GPS isn't (yet) ubiquitous is the real difficulty in location-aware development. The goal here is to get the location from any phone, not just GPS-capable phones. Therein lies the difficulty.

Google Can Do It, Why Can't We?

If you haven't already played with Google’s Mobile Maps, I highly recommend it. It’s really a handy little app, especially when you're on the go and need a map (happens all the time for me). But, Mobile Maps works on my phone, and I don't have GPS. How is that possible? Google has figured out how to determine your location without GPS, with their Mobile Maps, using common cell phone data. That means finding location is possible without GPS. In fact, let’s start our project by deconstructing how Google’s Mobile Maps works, and build it back up in C#.

DeepCast/google_mobile_maps.png

Protocol Analysis?

I won't go into details in this article about protocol or packet analyzers (I'm using Microsoft Network Monitor). Suffice it to say that the first step to deconstructing Google Mobile Maps is to analyze HTTP requests while Mobile Maps is running through ActiveSync.

A quick peek under the Mobile Map hood reveals a couple things. First, Google is posting requests to http://www.google.com/glm/mmap for location data. I've scoured their APIs, and this isn't something that appears to be documented. That means, we're on our own for figuring out how to package and send data. Secondly, it’s clear that they're sending four key pieces of data:

  • Cell Tower ID
  • Location Area Code (LAC)
  • Mobile Network Code (MNC)
  • Mobile Country Code (MCC)

That’s great news! As it turns out, almost all cell phones have this data readily available. Windows Mobile (5 and 6) has a native API to get those four pieces of data called the Radio Interface Layer.

Cell Tower Data - The Radio Interface Layer

We need to crack open the Windows Mobile Radio Interface Layer to get cell tower data. No worries, it’s not as complex as you might think. Plus, you can search for RIL on MSDN for details on the entire RIL library. It’s very well documented.

DeepCast/ril.gif

We're only interested in the cell tower portion of the RIL. The first thing we need to do is add the necessary PInvoke DLL Import signatures to call into the Radio Interface Layer.

[DllImport("ril.dll")] 
private static extern IntPtr RIL_Initialize(uint dwIndex, RILRESULTCALLBACK pfnResult, 
        RILNOTIFYCALLBACK pfnNotify, uint dwNotificationClasses, 
        uint dwParam, out IntPtr lphRil);
[DllImport("ril.dll", EntryPoint = "RIL_GetCellTowerInfo")]
private static extern IntPtr RIL_GetCellTowerInfo(IntPtr hRil);
[DllImport("ril.dll", EntryPoint = "RIL_Hangup")]
private static extern IntPtr RIL_Hangup(IntPtr hRil);
[DllImport("ril.dll")]
private static extern IntPtr RIL_Deinitialize(IntPtr hRil);

These four methods contained in ril.dll are our gateway to cell tower data. With these methods, and their backing RILRESULTCALLBACK and RILNOTIFYCALLBACK structures, we can easily call Windows for our cell tower data.

public static CellTower GetCellTowerInfo()
{
    IntPtr radioInterfaceLayerHandle = IntPtr.Zero;
    IntPtr radioResponseHandle = IntPtr.Zero;

    // Initialize the radio layer with a result callback parameter.
    radioResponseHandle = RIL_Initialize(1, new RILRESULTCALLBACK(CellDataCallback), 
        null, 0, 0, out radioInterfaceLayerHandle);

    // The initialize API call will always return 0 if initialization is successful.
    if (radioResponseHandle != IntPtr.Zero)
    { 
        return null;
    }

    // Query for the current tower data.
    radioResponseHandle = RIL_GetCellTowerInfo(radioInterfaceLayerHandle);

    // Wait for cell tower info to be returned since RIL_GetCellTowerInfo invokes the
    // callback method asynchronously.
    waithandle.WaitOne();

    // Release the RIL handle
    RIL_Deinitialize(radioInterfaceLayerHandle);

    // Convert the raw tower data structure data into a CellTower object
    return new CellTower()
    {
        TowerId = Convert.ToInt32(_towerDetails.dwCellID),
        LocationAreaCode = Convert.ToInt32(_towerDetails.dwLocationAreaCode),
        MobileCountryCode = Convert.ToInt32(_towerDetails.dwMobileCountryCode),
        MobileNetworkCode = Convert.ToInt32(_towerDetails.dwMobileNetworkCode),
    };
}

That was easy, thanks to MSDN! Now, we can call GetCellTowerInfo(), and we'll get a strongly typed CellTower class with all the tower details in return. I always prefer to work in a strongly typed representation of PInvoke output instead of marshaling pointers.

*Note: this code will not work in an emulator unless you have the Cellular Emulator configured and running as well. I suggest using your phone instead of the emulator.

Cell Data Services

Great, we have cell tower data, but now what? We still don't have GPS coordinates. We know that Google is making this translation happen, cell data to lat & long, so let’s continue to dissect Google Mobile Maps. Another look under their hood shows that they're posting binary data to the URL above. Again, analyzing protocols is outside the scope of this article. What you'll find is that we need to post our data in a 55 byte array. Most of the bytes are empty (0), but we do need to fill in a few key items in the array. Here’s the code to create and populate the byte array with our cell tower data:

private static byte[] GetFormPostData(int cellTowerId, int mobileCountryCode, 
        int mobileNetworkCode, int locationAreaCode)
{
    byte[] pd = new byte[55];
    pd[1] = 14;     //0x0e;
    pd[16] = 27;    //0x1b;
    pd[47] = 255;   //0xff;
    pd[48] = 255;   //0xff;
    pd[49] = 255;   //0xff;
    pd[50] = 255;   //0xff;

    // GSM uses 4 digits while UTMS used 6 digits (hex)
    pd[28] = ((Int64)cellTowerId > 65536) ? (byte)5 : (byte)3; 

    Shift(pd, 17, mobileNetworkCode);
    Shift(pd, 21, mobileCountryCode);
    Shift(pd, 31, cellTowerId);
    Shift(pd, 35, locationAreaCode);
    Shift(pd, 39, mobileNetworkCode);
    Shift(pd, 43, mobileCountryCode);

    return pd;
}

/// <summary>
/// Shifts specified data in the byte array starting at the specified array index.
/// </summary>
/// <param name="data">The data.</param>
/// <param name="startIndex">The start index.</param>
/// <param name="leftOperand">The left operand.</param>
private static void Shift(byte[] data, int startIndex, int leftOperand)
{
    int rightOperand = 24;

    for (int i = 0; i < 4; i++, rightOperand -= 8)
    {
        data[startIndex++] = (byte)((leftOperand >> rightOperand) & 255);
    }
}

In short, we can pass in our four parameters: Tower ID, LAC, MNC, and MCC, and we get back a byte array we can post to Google via HTTP. The code below demonstrates the whole HTTP request, which takes our strongly typed cell tower data (from the PInvoke above) and returns the latitude and longitude from Google’s database!

internal static GeoLocation GetLocation(CellTower tower)
{
    try
    {
        // Translate cell tower data into http post parameter data
        byte[] formData = GetFormPostData(tower.TowerId,
        tower.MobileCountryCode,
        tower.MobileNetworkCode,
        tower.LocationAreaCode);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
            new Uri(Google_Mobile_Service_Uri));
        request.Method = "POST";
        request.ContentLength = formData.Length;
        request.ContentType = "application/binary";

        Stream outputStream = request.GetRequestStream();

        // Write the cell data to the http stream
        outputStream.Write(formData, 0, formData.Length);
        outputStream.Close();

        HttpWebResponse response = (HttpWebResponse)request.GetResponse();

        return ReadResponse(response);
    }
    catch{}

    return GeoLocation.Empty;
}

private static GeoLocation ReadResponse(HttpWebResponse response)
{
    byte[] responseData = new byte[response.ContentLength];

    int bytesRead = 0;

    // Read the response into the response byte array
    while (bytesRead < responseData.Length)
    {
        bytesRead += response.GetResponseStream()
            .Read(responseData, bytesRead, responseData.Length - bytesRead);
    }

    // Check the response
    if (response.StatusCode == HttpStatusCode.OK)
    {
        int successful = Convert.ToInt32(GetCode(responseData, 3));

        if (successful == 0)
        {
            return new GeoLocation()
            {
                Latitude = GetCode(responseData, 7) / 1000000,
                Longitude = GetCode(responseData, 11) / 1000000
            };
        }
    }

    return GeoLocation.Empty;
}

/// <summary>
/// Gets the latitude or longitude from the byte array.
/// </summary>
/// <param name="data">The byte array.</param>
/// <param name="startIndex">The start index.</param>
/// <returns></returns>
private static double GetCode(byte[] data, int startIndex)
{
    return ((double)((data[startIndex++] << 24) |
        (data[startIndex++] << 16) |
        (data[startIndex++] << 8) |
        (data[startIndex++])));
}

A Brief Cell Tower Explanation

Let me take a moment to describe what just happened. We read our phone’s cell tower data from the OS, sent it off to Google via HTTP, and got back the latitude and longitude. How is that even possible?

The FCC keeps record of each cell tower in the US. Google isn't doing anything (too) magical. They're simply looking up the details for the tower we're submitting, and returning the tower’s well known location in lat/long format.

Google is not the only database of tower locations, however. OpenCellID.org is an open source initiative to map all cell towers and their GPS coordinates. Yahoo! is doing the same thing with their ZoneTag service.

Why go through all the trouble to use Google if there are easier alternatives?

That’s a great question! Simply put, Google is the most comprehensive database. OpenCellID and ZoneTag are newer, and their databases aren't complete. I can't even get a valid GPS result here at home, a suburban area in a major city, with either of these “beta” services… but, Google works everywhere I've tested so far.

What if my Phone has GPS?

Naturally, cell tower data isn't as geo-precise as GPS. You can't tell from your cell tower data if you are 10 feet or 1000 feet from the tower. In other words, it’s almost always preferable to use GPS when it’s available, because of its precision. There are exceptions like when you're indoors and can't get a GPS signal; you'll likely still have a cell tower connection.

Getting GPS data from a compatible phone is even easier than getting cell tower data. Even better – Microsoft has distributed a library to do all the heavy lifting. All we have to do is subscribe to the event that’s fired when the Microsoft GPS class detects a location change. Microsoft has done the rest.

private Gps _gps = new Gps();
private GpsPosition _currentPosition;

public void Start()
{
    _gps.LocationChanged += new 
      Microsoft.Location.LocationChangedEventHandler(_gps_LocationChanged);

    if (!_gps.Opened)
    {
        _gps.Open();
    }
}

private void _gps_LocationChanged(object sender, 
        Microsoft.Location.LocationChangedEventArgs args)
{
    _currentPosition = args.Position;
}

As the folks at Staples say, “That was easy.” DeepCast/easy-button.jpg

The Holy Grail?

So far, we've built two mechanisms to get your phone’s latitude and longitude. We have as much potential now as, say, Google Maps! It would seem like that’s a great place to do a victory dance, submit the code, and have a glass of eggnog (it’s 5 days until Christmas while I'm writing).

But, what good is location anyway?

I generally know where I am, so knowing my location doesn't help much. Applications like Google Mobile Maps already show me my location, but they don't do anything else with it. I just don't think the article can end here… not yet.

It’s my opinion that the real value of location based services is in sharing your location, not just knowing your location. This is the next inevitable step in social networking.

Making Use of Location

I'm a firm believer in the creative process, and there are lots of developers who are more creative than me. I'm going to shift direction a bit and try and provide creative influence rather than a concrete solution. Why don't you let the rest of this article be your mobile location muse? You should come up with the next great idea on what to do with your location.

My first creativity catalyst is updating your Yahoo! Fire Eagle account with your latitude and longitude. Fire Eagle is a new service from Yahoo! that takes in your location and “broadcasts” it to the applications you've subscribed to (in their rapidly growing gallery).

Actually, that’s the reason this project was named DeepCast. We're broadcasting our location to a (potentially) deep list of services and providers. Not just a broadcast… a deep cast =).

Fire Eagle

Fire Eagle uses Oauth as its authorization platform. I'm really not all that familiar with all the details of Oauth. What I do know is that there’s a manual process for authorization before any application can make updates.

The first step in Oauth authentication is to generate a request token and secret pair.

DeepCast/dc2.png

Then, you can browse (via your desktop browser) to the authorization URL.

DeepCast/dc3.png

You have to manually confirm that the application can update your profile.

DeepCast/yfe.png

Finally, you have to exchange your request token and secret for an authorization token and secret. These are the values you need to save and use every time the application attempts to update your location.

DeepCast/dc4.png

Now that you have an authorization token and secret pair, you can update Fire Eagle as often as you like. Fire Eagle will then broadcast your new location to the underlying applications you've subscribed to.

Twitter

I've never been the biggest fan of Twitter. It just takes too much work to get too little useful information (just my opinion…). But, what if Twitter could show me where people are?!

What if we could post a tweet with a link to a Google map showing your location? That seems to have some utility.

DeepCast/dc5.png

There’s very little to describe in terms of updating Twitter. It’s as simple as making an HTTP request. The one difficulty is adding a hyperlink to the message. Twitter uses a form of URL encoding that truncates most query strings. We'll generate a TinyUrl to get around that issue.

Here’s how we update Twitter:

internal override void Update(GeoLocation location)
{
    if (!string.IsNullOrEmpty(Settings.TwitterEmail) && 
        !string.IsNullOrEmpty(Settings.TwitterPassword))
    {
        string googleMapLink = string.Format(Google_Map_Uri, 
                               location.Latitude, location.Longitude);
        string tilyMapLink = TinyUrlService.GetTinyUrl(googleMapLink);
        string place = string.Format("I'm here: {0} - 'L:{1}, {2}'", 
            new object[] { tilyMapLink, location.Latitude, location.Longitude });

        string data = string.Format("status={0}", place);

        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Twitter_Api_Uri);
        request.Credentials = new NetworkCredential(Settings.TwitterEmail, 
                                  Settings.TwitterPassword);
        request.ContentType = "application/x-www-form-urlencoded";
        request.Method = "POST";
        request.AllowWriteStreamBuffering = true;

        byte[] bytes = Encoding.UTF8.GetBytes(data);

        request.ContentLength = bytes.Length;

        using (Stream requestStream = request.GetRequestStream())
        {
            requestStream.Write(bytes, 0, bytes.Length);
            requestStream.Flush();
            requestStream.Close();

            using (WebResponse response = request.GetResponse())
            {
                using (StreamReader reader = 
                    new StreamReader(response.GetResponseStream()))
                {
                    reader.ReadToEnd();
                }
            }
        }
    }
}

DeepCast/twt.png

Note that there’s a location convention, ‘L: lat, lon’, that’s got a deeper purpose. That format can be parsed by the folks at TwitterMap.com. Again, we're trying to make location useful to as deep an audience as possible.

Internal Services

Naturally, sharing your location isn't the only way to make the information useful. The project has been structured in a way that location can be used internally as well as externally. The main page, for example, makes use of the same location that the external-facing services use. The map on the main page is simply a browser control, with the URL being set to a Yahoo! Maps image. Nice and simple, which is what I wanted!

DeepCast/dc1.png

Your Service Goes Here

That’s right – it’s your turn. Most of the project has been stubbed out for you to add your own location-aware service. If you've read this far in the article, you're probably interested in location services, so feel free to modify the project and code away! I'm encouraging all readers to use this project as a stepping stone to the next big idea. Some of the possible uses for location I can think of are:

  • Track where you go via a web service. Create a backing website that shows a map of your tracks - when and where you were. Draw a history of where you've traveled.
  • Track your kids so you always know where they are.
  • Geo-tag your photos. Flickr supports geo-tagged images, for example.
  • Log where your fleet is (i.e., FedEx delivery employees).
  • Geo-blogging.
  • Update your Facebook/MySpace/Social Site with your location.
  • Seriously, it’s your turn now!

Thanks for reading, and happy geo-coding!

History

  • 22nd December, 2008 - Article submitted
  • 8th February, 2009 - All user-submitted bug fixes incorporated, Twitter HTTP 417 exception fixed
    • Huge thanks to pejonsson for his suggestions!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here