Академический Документы
Профессиональный Документы
Культура Документы
Introduction Why is Location Hard? Google's Approach o Protocol Analysis o Radio Interface Layer Cell Data Services o A Brief Cell Tower Explanation What if my Phone already has GPS? Making Use of Location o Yahoo's Fire Eagle o Twitter o Internal Services o Your Service Goes Here! History
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).
cell phone data. That means finding location is possible without GPS. In fact, lets start our project by deconstructing how Googles Mobile Maps works, and build it back up in C#.
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, its 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)
Thats 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.
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. Collapse
[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. Collapse
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.
pd[1] = 14; pd[16] = 27; pd[47] = 255; pd[48] = 255; pd[49] = 255; pd[50] = 255;
// GSM uses 4 digits while UTMS used 6 digits (hex) pd[28] = ((Int64)cellTowerId > 65536) ? (byte)5 : (byte)3; Shift(pd, Shift(pd, Shift(pd, Shift(pd, Shift(pd, Shift(pd, 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); } } 17, 21, 31, 35, 39, 43, mobileNetworkCode); mobileCountryCode); cellTowerId); locationAreaCode); mobileNetworkCode); mobileCountryCode);
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 Googles database! Collapse
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++]))); }
if (!_gps.Opened) { _gps.Open(); } } private void _gps_LocationChanged(object sender, Microsoft.Location.LocationChangedEventArgs args) { _currentPosition = args.Position; }
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 theres 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.
Then, you can browse (via your desktop browser) to the authorization URL.
You have to manually confirm that the application can update your profile.
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.
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.
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.
Theres very little to describe in terms of updating Twitter. Its 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. Heres how we update Twitter: Collapse
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(); } } } } }
Note that theres a location convention, L: lat, lon, thats 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!
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, its your turn now!
History
22nd December, 2008 - Article submitted 8th February, 2009 - All user-submitted bug fixes incorporated, Twitter HTTP 417 exception fixed
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)