Using the Location Service in Silverlight for Windows Phone

One of the most exciting features of the Windows phone from a developer’s perspective is the location service. The location service is a set of APIs that rely on Assisted-GPS (A-GPS), Wi-Fi Positioning System (WPS), and cell-site triangulation to expose location data to an application. Simply put, this means that a Windows phone app can determine where it’s at at almost any time, excepting situations where GPS satellites aren’t reachable, cell towers aren’t in range, and no Wi-Fi signals are available. Combined with Microsoft’s Bing Maps Silverlight control for Windows Phone, the location service opens the door to an entire genre of apps that wouldn’t be possible otherwise.

The core of the location API is embodied in a class named GeoCoordinateWatcher, which lives in the System.Device.Location namespace of the System.Device assembly. The first thing you do to build a phone app that uses location is add a reference to System.Device to your project. Then you create an instance of GeoCoordinateWatcher, like this:

The value passed to the constructor specifies the accuracy that you desire in your location data. The two possible values are GeoPositionAccuracy.Default and GeoPositionAccurary.High. According to the documentation, GeoPositionAccuracy.Default means “Optimize for power, performance, and other cost considerations,” while GeoPositionAccuracy.High means “Deliver the most accurate report possible. This includes using services that might charge money, or consuming higher levels of battery power or connection bandwidth.” Translated, this means that Default will ignore GPS even if it’s available, trading accuracy for lower power consumption, while High will attempt to use GPS to deliver the most accurate data possible at the expense of increased power consumption.

The next step after instantiating a GeoCoordinateWatcher object is to set its MovementThreshold property:

watcher.MovementThreshold = 20; // 20 meters

Once more the documentation is a bit vague about what this means, but a simple way to think about it is that the lower the MovementThreshold, the more often your app will be pinged with location data. The downside to lower values is increased power consumption. You can set MovementThreshold to 0 if you’d like, but Microsoft’s Location Programming Best Practices for Windows Phone recommends a minimum setting of 20 meters. If you’re building a navigation app to be used while driving, 20 meters is a reasonable setting because a car can cover 20 meters in a hurry. But if you’re building an app to track the location of someone running or walking, you might want to set MovementThreshold to something lower.

The next step is to register handlers for GeoCoordinateWatcher’s StatusChanged and PositionChanged events:

StatusChanged fires whenever the location status changes – for example, when the location service acquires or loses your position. PositionChanged events occur whenever your position changes, and their frequency is determined by 1) the value of the MovementThreshold property, and 2) how fast you’re traveling. In my experience, PositionChanged events never fire faster than about once per second, but if you set MovementThreshold to 20 and then stand in place, one or two PositionChanged events will fire and then won’t fire again until you’ve moved 20 meters or so.

Once the event handlers are registered, you start the location service by calling GeoCoordinateWatcher.Start:

watcher.Start();

You’ll receive a StatusChanged event almost immediately indicating that the service is initializing, and shortly thereafter you’ll receive another StatusChanged event telling you that the location service is “Ready” (assuming, of course, it was able to acquire a source for location data). If you have a cell signal or are connected to a wireless network, the Ready signal will typically come within a second or two. If the location service must wait for a GPS lock, however, it may be a minute or more before the service says it’s ready.

Shortly after you call GeoCoordinateWatcher.Start, you’ll start receiving PositionChanged events if indeed the location service was able to find a source of location data. Each PositionChanged event is accompanied by a GeoPositionChangedEventArgs which exposes properties named Position and Timestamp. The Position property exposes a subproperty named Location. Through the Location property, you can access these properties:

Keep in mind that the values of any of these properties can be Double.Nan, which means no data is currently available for that property. For example, altitude can only be determined through GPS, so if the location service is currently getting location data by triangulating cell signals, Latitude and Longitude will contain valid (though not terribly accurate) values, but altitude will be “Not a Number.” That could change in the next PositionChanged event if the location service manages to acquire a GPS lock in the meantime.

There is no property or method that you can call to determine how the location service is getting its data, but you can infer whether the data is coming from GPS by checking the HorizontalAccuracy and VerticalAccuracy properties. When the phone has a GPS connection, these values will typically fall between 4 and 7 meters. When the location data comes from non-GPS sources, however, HorizontalAccuracy and VerticalAccuracy can be 100 meters or more – or even NaN.

Because the location service’s status can change often, especially if you’re driving down the interstate going under bridges and going in and out of contact with cell towers, it’s useful to process StatusChanged events to let the user know what’s going on. StatusChanged events are accompanied by GeoPositionStatusChangedEventArgs whose Status property can be any one of four values:

GeoPositionStatus.Disabled, which indicates that the location service has been disabled by the user

GeoPositionStatus.Initializing, which indicates that the location service is trying to connect to a source of data

GeoPositionStatus.Ready, which indicates that the location service is receiving location data

GeoPositionStatus.NoData, which indicates that although the location service is enabled, no location data is currently available

If you receive a StatusChanged event indicating that the location service is disabled, you might want to pop up a message box informing the user that they’ll need to go into their phone’s settings and enable the location feature. On the other hand, if the service is up and running but suddenly loses contact with the source (or sources) of location data, you may want to let the user know by responding to StatusChanged events with a status of GeoPositionStatus.NoData.

You could easily write an app that displays a textual readout of the data emanating from a GeoCoordinateWatcher, but that would be boring. Which is one reason why Silverlight for Windows Phone includes a handy Map control. The Map control lives in the Microsoft.Phone.Controls.Maps namespace of the Microsoft.Phone.Controls.Maps assembly. Once you’ve added a reference to that assembly to your project, declaring a Map control is no more difficult than this:

<maps:Map x:Name="BingMap" CopyrightVisibility="Collapsed"

ZoomBarVisibility="Visible" ZoomLevel="17" Mode="AerialWithLabels"

CredentialsProvider="…" Center="47.6741667,-122.1202778" />

The Map control’s Center property centers the map at the specified latitude and longitude. Rather than hard-code this value, you could get the current location from the location service and assign it to Center. Better yet, pass the Map a new latitude and longitude every time GeoCoordinateWatcher fires a PositionChanged event, and the map will follow you as you move around.

Before you can use the Map control, you must go to the Bing Maps Account Center and obtain an API key. It’s free and it’s fast. The key is a long string of letters and numbers that you assign to the control’s CredentialsProvider property. The API key comes with a few restrictions, but in general, most apps – even apps that are purchased rather than free – can use it without charge. You can review the terms of use online.

Want pushpins to pinpoint locations on the map? That’s easy, too:

<maps:Map x:Name="BingMap" CopyrightVisibility="Collapsed"

ZoomBarVisibility="Visible" ZoomLevel="17" Mode="AerialWithLabels"

CredentialsProvider="…" Center="47.6741667,-122.1202778">

<maps:Pushpin x:Name="Pin" Background="Red" />

</maps:Map>

By default, a pushpin renders itself as a 4-sided polygon that somewhat resembles a flag. Because Pushpin is a content control, you can stuff arbitrary content inside it by assigning that content to the Content property. Or you can reshape the pushpin by retemplating it. The following XAML declares a pushpin and retemplates it so it appears as a red ellipse:

I built an app that combines the location service with a Map control to show where you are – and which direction you’re headed – in real time. When the app starts up, it initializes a GeoCoordinateWatcher. When the location data starts flowing, the app shows your current location with a templated pushpin that looks like an arrow. And when a PositionChanged event containing a non-NaN Course property arrives (that’ll happen after you’ve moved a block or two), the arrow rotates to show your direction of travel. Although not shown in the screen capture below, the display also includes a small textual readout revealing the horizontal and vertical accuracies reported in the PositionChanged events. Initially these values will be pretty high (or NaN), but if you move outside so the phone can acquire a GPS lock, you’ll see the numbers drop down to something more reasonable like 4 meters.

The source code is shown below. I set GeoCoordinateWatcher’s MovementThreshold to 10 meters, so if you’re driving, you’ll receive map updates once a second or so, but if you’re walking, updates will be less frequent. You can lower the threshold to generate more frequent updates, but remember that lower MovementThresholds consume battery power faster. A fun addition to the app would be a slider that allows the user to set the MovementThreshold. That way, a user who is driving could select a rather high value, while a user out for a morning run could select something finer.

Note how I used VisualTreeHelper to find the RotateTransform assigned to the control template for the pushpin. One of the nuances of retemplating controls in Silverlight (and Silverlight for Windows Phone) is that you can’t reference named elements in a template the same way you reference named elements elsewhere in the XAML. Instead, you use VisualTreeHelper to examine the elements generated from the template and walk the tree from top to bottom until you find the element you’re looking for. This is standard practice in Silverlight apps, and it works in Silverlight for Windows Phone, too.

You can download the source code and try the app for yourself. Take a drive around the block and watch the map update as you do. (I did, and I did so legally because Tennessee has no laws against programming while driving.) The only caveat is that before you can use the app, you’ll need to plug your own Bing Maps API key into CredentialsProvider. I considered including mine, but since I’m disseminating the source code, I didn’t want to hand out my API key, too. Enjoy!