Windows Phone 7 Silverlight: Location Services

Exclusive offer: get 50% off this eBook here
Windows Phone 7 Silverlight Cookbook

Windows Phone 7 Silverlight Cookbook — Save 50%

All the recipes you need to start creating apps and making money with this Microsoft Windows Phone 7 Silverlight book and eBook

$29.99    $15.00
by Jonathan Marbutt Robb Schiefer Jr. | September 2011 | Cookbooks Microsoft

In this article by Jonathan Marbutt and Robb Schiefer Jr., authors of Windows Phone 7 Silverlight Cookbook, we will take a deep dive into the location API for Windows Phone 7 by building an application to help navigate during travel and another to map the user's location.

In this article we will cover:

  • Tracking latitude and longitude
  • Tracking altitude, speed, and course
  • Saving battery by using a location wisely
  • Using location services with the emulator
  • Mapping your location

 

(For more resources on this subject, see here.)

 

Introduction

One of the most powerful features of smartphones today is location awareness. Windows Phone 7 is no exception. The wide consumerization of GPS around 10 years ago brought handheld GPS receivers for consumers on the go, but few individuals could justify the expense or pocket space. Now that smartphones have GPS built in, developers have built incredibly powerful applications that are location-aware. For example, apps that help users track their jogging route, get real-time navigation assistance while driving, and map/analyze their golf game.

In this article, we will take a deep dive into the location API for Windows Phone 7 by building an application to help navigate during travel and another to map the user's location.

Tracking latitude and longitude

In this recipe, we will implement the most fundamental use of location services, tracking latitude and longitude. Our sample application will be a navigation helper which displays all the available location information. We will also review the different ways in which the phone gets its location information and their attributes.

Getting ready

We will be working in Visual Studio for this tutorial, so start by opening Studio and creating a new Windows Phone 7 application using the Windows Phone Application project template.

All the location/GPS-related methods and classes are found in the System.Deviceassembly, so add this reference next:

Windows Phone 7 Silverlight: Location Services

We will need some UI to start tracking and displaying the data, so go to the MainPage.xaml file, if it's not already open. Change the ContentPanel from a Grid to a StackPanel, then add a button to the designer, and set its Content property to Start Tracking. Next add four TextBlocks. Two of these will be Latitude and Longitude labels. We will use the others to display the latitude/longitude coordinates, so set their x:Name properties to txtLatitudeand txtLongitude respectively. You can also set the application and page titles if you like. The resulting page should look similar to the following screenshot:

Windows Phone 7 Silverlight: Location Services

How to do it...

The core class used for tracking location is the GeoCoordinateWatcher. We subscribe to the events on the watcher to be noticed when changes occur:

  1. Double-click on your button in the designer to go to the click event handler for the button in the code behind file. This is where we will start watching for location changes.
  2. Create a GeoCoordinateWatcher field variable named _watcher. Set this field variable inside your click event handler to a new GeoCoordinateWatcher. Next add a handler to the PositionChanged event named _watcher_PositionChanged. Then start watching for position changes by calling the Start method.
  3. Next add a handler to the PositionChanged event named _watcher_PositionChanged. Then start watching for position changes by calling the Start method.
  4. In order to use the position information, create the void handler method with parameters named sender of type object and e of type GeoPositionChangedEventArgs<GeoCoordinate>. Inside this method set the Text properties on the txtLatitude and txtLongitude text boxes to the coordinate values e.Position.Location.Latitude and e.Position.Location.Longitude respectively.

Latitude and longitude as strings:
Latitude and longitude are of type double and can be converted to strings using the ToString method for display.

You should end up with a class that is similar to the following block of code:

public partial class MainPage : PhoneApplicationPage
{
private IGeoPositionWatcher<GeoCoordinate> _watcher;
public MainPage()
{
InitializeComponent();
}
private void butTrack_Click(object sender, RoutedEventArgs e)
{
_watcher = new GeoCoordinateWatcher();
_watcher.PositionChanged += _watcher_PositionChanged;
_watcher.Start();
}
void _watcher_PositionChanged(object sender,
GeoPositionChangedEventArgs<GeoCoordinate> e)
{
txtLatitude.Text = e.Position.Location.Latitude.ToString();
txtLongitude.Text = e.Position.Location.Longitude.
ToString();
}
}

That's it. You can now deploy this app to your phone, start tracking, and see the latitude and longitude changes on your screen.

How it works...

The watcher starts a new background thread to watch for position changes. Each change is passed to your event handler(s) for processing.

Window Phone 7 provides location services through the following three sources:

  • GPS: Satellite based
  • Wi-Fi: Known wireless network positions
  • Cellular: Cellular tower triangulation

Each of these position providers has their strengths and weaknesses, but the combination of the three covers nearly any possible use case:

  • GPS is the most accurate, but you must have an unobstructed view of the sky
  • Wi-Fi can be accurate depending on how close you are to the access point, but you must be in the range of a known wireless network
  • Cellular is the least accurate and only needs cell signal

So if you're in an urban area with tall buildings, GPS may be intermittent but Wi-Fi networks and cellular coverage should be plentiful. If you are in a rural area, GPS should work well and cellular triangulation might help where available.

Tracking altitude, speed, and course

In this section, we will discuss the different types of location information that are provided by the GeoCoordinateWatcher and how they might be used. A quick look at the Object Browser shows us that the GeoCoordinate object has several interesting properties:

Windows Phone 7 Silverlight: Location Services

In addition to Latitude and Longitude, there is Altitude, Speed, and Course, among others. Altitude and Speed are pretty self-explanatory, but Course might not be as obvious. Course is your heading or the direction you are going, given two points. The following table shows each property and its unit of measurement:

Windows Phone 7 Silverlight: Location Services

Horizontal and Vertical Accuracy specifies the accuracy of Latitude/Longitude and Altitude, respectively, in meters. For example, this means your actual Latitude position is between the reported Latitude minus the accuracy value and the reported Latitude plus the accuracy value. The smaller the accuracy value, the more accurate but the longer it may take to get a position.

Getting ready

Add three more sets of TextBlock controls under the longitude control for each of the following properties: Altitude, Speed, and Course. Set the speed label TextBlock Text property to Speed (mph). Name the TextBlock controls as you did for latitude/longitude so we can assign their Text properties from the code behind. The page should look similar to the following screenshot:

Windows Phone 7 Silverlight: Location Services

How to do it...

Perform the following steps to add altitude, speed, and course to the application:

  1. Open the code behind file for the page, and in the positionChanged handler, set Altitude in the same way as we did for latitude/longitude before; simply set the Text property of the txtAltitude TextBlock to the Altitude property as a string.
  2. For the Speed property, convert from meters per second to miles per hour. One meter/sec equals 2.2369363 miles per hour, so we can multiply the Speed property by 2.2369363 to get miles per hour.
  3. Display Course so that the normal users can understand it, using the name of the direction (that is, North, South, East, West). The Course value is a degree value from 0 to 360, where 0/360 is north and the degrees go clock-wise with a compass.
  4. Create a series of if statements that will provide the correct heading. Between 316 and 45 will be North, 46 and 135 will be East, 136 and 225 will be South, and between 226 and 315 will be West. Our _watcher_PositionChanged method is now as follows:

    void _watcher_PositionChanged(object sender, GeoPositionChangedEve
    ntArgs<GeoCoordinate> e)
    {
    txtLatitude.Text = e.Position.Location.Latitude.
    ToString()
    txtLongitude.Text = e.Position.Location.Longitude.
    ToString();
    txtAltitude.Text = e.Position.Location.Altitude.
    ToString();
    txtSpeed.Text = (e.Position.Location.Speed *
    2.2369363).
    ToString();
    double course = e.Position.Location.Course;
    string heading = string.Empty;
    if (course >= 46 && course <= 135)
    heading = "East";
    if (course >= 136 && course <= 225)
    heading = "South";
    if (course >= 226 && course <= 315)
    heading = "West";
    else
    heading = "North";
    txtCourse.Text = heading;
    }

How it works...

If you deploy the application to your phone now, you will see Speed display NaN (Not a Number) , Altitude display zero, and Course is blank. This is because Altitude, Speed, and Course are only available when you specify that you want high accuracy location information. We do this by instantiating the GeoCoordinateWatcher with a GeoPositionAccuracy type of GeoPositionAccuracy.High in the constructor. By default, the accuracy is set to GeoPositionAccuracy.Default, which only uses cellular triangulation and is not accurate enough to calculate speed, altitude, or course. GeoPositionAccuracy.High uses GPS and Wi-Fi, when available, which provides more accurate positions. Although it is more accurate, it also uses more power and can take longer to get your position. This is why High is not the default. It is strongly recommended that you only use the higher accuracy when it is absolutely needed.

In this case, we need the Altitude, Speed, and Course, so it is necessary. Set the accuracy level to high in the GeoCoordinateWatcher constructor , like so:

_watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);

If you redeploy the application to the phone, you may notice it still shows NaN for Speed. This may be because you are indoors and have an obstructed view of the sky or it may just take a few moments to get a good signal. Once you have a good GPS signal, you should see valid Speed, Altitude, and Course values. The best way to test this application is in the passenger seat of a driving vehicle so you can compare the vehicles, speedometer to the speed in the application.

There may be times, as well, when you lose GPS signal. When this occurs, the latitude and longitude values will also be set to NaN. In such cases, you may want to give the user a friendlier explanation of the problem. You can simply check the IsUnknown property in the position changed event and provide a better message. For example:

void _watcher_PositionChanged(object sender, GeoPositionChanged
EventArgs<GeoCoordinate> e)
{
if (e.Position.Location.IsUnknown)
{
txtLatitude.Text = "Finding your position.
Please wait ...";
txtLongitude.Text = "";
txtAltitude.Text = "";
txtSpeed.Text = "";
txtCourse.Text = "";
return;
}
txtLatitude.Text = e.Position.Location.Latitude.
ToString();
txtLongitude.Text = e.Position.Location.Longitude.
ToString();
txtAltitude.Text = e.Position.Location.Altitude.
ToString();
txtSpeed.Text = (e.Position.Location.Speed * 2.2369363).
ToString();
double course = e.Position.Location.Course;
string heading = string.Empty;
if (course >= 46 && course <= 135)
heading = "East";
if (course >= 136 && course <= 225)
heading = "South";
if (course >= 226 && course <= 315)
heading = "West";
else
heading = "North";
txtCourse.Text = heading;
}

The last property we will cover in this recipe is the Permission property on the GeoPositionWatcher. Before submitting your app to the marketplace, you must define which phone capabilities your app requires. One of those capabilities is location. Before a user installs an application, he/she is informed of the capabilities the app requires and must accept them to install. Even though the user has given the app permission to use location services of the phone, the user can still turn off location services for all apps from the settings menu. The Permission property will help us check for this and tell the user why the app isn't working.

There is a slight trick though; the Permission property will be set to Granted when the watcher is first created, even if Location services are disabled in the Settings menu. It will be reset to Denied after the Start method is called. So we must check for a Denied permission value after calling the Start method. For instance:

private void butTrack_Click(object sender, RoutedEventArgs e)
{
_watcher = newGeoCoordinateWatcher(GeoPositionAccuracy.High);
_watcher.PositionChanged += _watcher_PositionChanged;
_watcher.StatusChanged += _watcher_StatusChanged;
_watcher.Start();
if (_watcher.Permission == GeoPositionPermission.Denied)
tbLatitude.Text = "Please enable location services and retry";
}

We can test this by turning off location services. From the start screen, flick left to the App list, tap Settings, and then tap location. Swipe the switch left to the Off position. Redeploy the application to your phone, click the Start Tracking button, and you will see our new message.

As mentioned previously, the user must accept the capabilities of the application before installing it. There may be future updates to the phone which allow the user to change the allowed capabilities of individual apps from the settings menu as well. The Permission property would also be useful in this scenario.

There's more...

You may have also noticed the CivicAddressResolver and CivicAddress classes in the System.Device.Location namespace . As its name implies, the CivicAddressResolver returns an address from a GeoCoordinate. Unfortunately, this is not yet implemented for Windows Phone. You can instantiate them and attempt to use them, but the returned CivicAddress will always be unknown. Hopefully, this will be implemented in the future updates of the operating system.

Windows Phone 7 Silverlight Cookbook All the recipes you need to start creating apps and making money with this Microsoft Windows Phone 7 Silverlight book and eBook
Published: August 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

 

(For more resources on this subject, see here.)

Saving battery by using a location wisely

As was previously mentioned, when setting the GeoPositionAccuracy to high, location services can use more power than normal phone operations and thus drain the battery. Many mobile developers struggle to build location-aware applications that are power efficient, but the Windows Phone 7 platform has the necessary hooks to make it work. This section will give you the tips you need to get it right.

Let's take our sample application and add the code to use the location efficiently.

Getting ready

Add a second button under the track button with the text Stop Tracking. We also need to show the user what the current status of the watcher is, so add a set of TextBlock controls above latitude, labeled Status:

Windows Phone 7 Silverlight: Location Services

How to do it...

Perform the following steps starting in the MainPage.xaml file:

  1. Double-click the Stop Tracking button in the designer windows to add a click event handler. Check to see if the _watcher field variable is null; if it isn't, stop it with the Stop method. This will allow the user to stop tracking:

    private void butStopTracking_Click(object sender,
    RoutedEventArgs e)
    {
    if (_watcher != null)
    _watcher.Stop();
    }

    We can use the StatusChanged event to update the status TextBlock appropriately. This event is called when the watcher status changes. The Status property is a GeoPositionStatus enumeration, with the following values:

    Windows Phone 7 Silverlight: Location Services

  2. Add a StatusChanged event handler to the watcher under the assignment of the PositionChanged handler:

    _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High);
    _watcher.PositionChanged += _watcher_PositionChanged;
    _watcher.StatusChanged += _watcher_StatusChanged;
    _watcher.Start();

  3. In the handler method, update the status value TextBlock with the new status found in the Status property of the GeoPositionStatusChangedEventArgs parameter:

    void _watcher_StatusChanged(object sender,
    GeoPositionStatusChangedEventArgs e)
    {
    tbStatus.Text = e.Status.ToString();
    }

  4. Let's add another Button to the bottom of the page with the text Get Last Position. Double-click on the button in the designer to create and go to the click event handler in the code behind.
  5. In the handler, we should check to see if the watcher is instantiated and then if the Position property is valid, by checking the watcher for null or the IsUnknown property on the Location property for false. If either is true, set the status value TextBlock to No previous position and return. Otherwise, display all the location information as we would for a position change and set the status value to the Position Timestamp property.
  6. To avoid duplication, let's refactor the setting of the display fields from the PositionChanged method handler to a new method that can be called by the PositionChanged and get the previous position button click handlers:

    void _watcher_PositionChanged(object sender,
    GeoPositionChangedEventArgs<GeoCoordinate> e)
    {
    if (e.Position.Location.IsUnknown)
    {
    ClearUi();
    txtLatitude.Text = "Finding your position.
    Please wait ...";
    return;
    }
    DisplayPosition(e.Position.Location);
    }
    private void DisplayPosition(GeoCoordinate location)
    {
    txtLatitude.Text = location.Latitude.ToString();
    txtLongitude.Text = location.Longitude.ToString();
    txtAltitude.Text = location.Altitude.ToString();
    txtSpeed.Text = (location.Speed * 2.2369363).ToString();
    double course = location.Course;
    string heading = string.Empty;
    if (course >= 46 && course <= 135)
    heading = "East";
    if (course >= 136 && course <= 225)
    heading = "South";
    if (course >= 226 && course <= 315)
    heading = "West";
    else
    heading = "North";
    txtCourse.Text = heading;
    }
    private void butGetLastPosition_Click(object sender,
    RoutedEventArgs e)
    {
    if (_watcher == null || _watcher.Position.Location.IsUnknown)
    {
    tbStatus.Text = "No previous position";
    return;
    }
    DisplayPosition(_watcher.Position.Location);
    tbStatus.Text = _watcher.Position.Timestamp.ToString();
    }

  7. We also need to store the watcher in a field variable and check for null instead of creating a new watcher each time the Start Tracking button is clicked:

    if (_watcher == null)
    {
    _watcher = new GeoCoordinateWatcher();
    _watcher.PositionChanged += _watcher_PositionChanged;
    _watcher.StatusChanged += _watcher_StatusChanged;
    }

  8. Set the MovementThreshold to 40 for our sample app, during instantiation of the GeoCoordinateWatcher:

    _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.High)
    {
    MovementThreshold = 40
    };

How it works...

When using location services, the first tip is to only watch for position changes when you need them. In this case, our sample app had partially taken that tip into account by only starting to watch for position changes after the user clicks the Start Tracking button. In this recipe, we also provided a way for the user to turn tracking off.

The next tip is to create as few watchers as possible, preferably one which you can reuse across the application. This will allow you to reuse the last known position from the watcher on any page. There is a Position property on GeoCoordinateWatcher which is set each time a new position is found; it is always the last known position. Few applications need to constantly track position changes. It is more likely that the application needs to only know a location occasionally. The Position propert y is a GeoPosition<GeoCoordinate> type which stores both the GeoCoordinate and the timestamp that is/was observed. If necessary, the application can determine if the coordinate is too old; in most cases it will not be.

So in this recipe we added a button for the user to get their last known position. We also refactored the start tracking button handler to use a single watcher instead of creating a new watcher each time the track button is clicked.

The last battery-saving tip is to adjust the accuracy of the watcher to your needs. We have already discussed setting the GeoPositionAccuracy to High only when needed. Using the default accuracy will help tremendously, but if high accuracy is necessary there is another setting that you should configure as well.

The MovementThreshold property on the GeoCoordinateWatcher allows you to set how often you wish to be notified of position changes. By default, it is set to zero, which notifies as often as there is a detected change in position. In fact, the PositionChanged event is fired with slightly different position values every second, even if you are standing still. This produces quite a bit of noise. It is common to set the threshold to at least 20 to get more meaningful position data. You should set the threshold according to the needs of your application.

In this recipe, we set the threshold to 40.

Saving battery while using Location Services is all about configuring the watcher appropriately. Using the tips outlined in this recipe will help you make the best decisions for your application. The biggest mistake developers make with location services is not understanding how they are getting position data and not dialing down the accuracy for their needs. You will not make that mistake if you put these tips into practice.

Using location services with the emulator

Up to this point, we have only tested our sample application on a phone. If you run the application from the previous recipe in the emulator, you will find the status of the GeoCoordinateWatcher would be set to NoData when attempting to track position. This is because the emulator has no GPS receiver and does not support using location service natively.

There are several reasons you may need to use location services in the emulator. The most obvious is you do not have a Windows Phone. Although, there are many times during development of an application that it is just impractical to test location services on a phone. If, for instance, you are developing an informational bus route application, you cannot travel all over the city every time you wish to test a small feature. Or perhaps, if you are creating a national golf course locator, it's impractical to travel all over the country to test your app.

Fortunately, Location Services abstracts its API with the IGeoPositionWatcher interface. This interface defines the core of location services:

Windows Phone 7 Silverlight: Location Services

As you can see, it defines the methods, properties, and events we have been using up to this point. In this recipe, we will create our own fake implementation of this interface to use in the emulator.

Getting ready

We will continue using our navigation application in this recipe. Add a Framework folder in the root of the project and then add a class to that folder named FakeGeoCoordinateWatcher. This will be our new, fake implementation of the watcher. The IGeoPositionWatcher interface is generic, allowing you to define a location type. The GeoCoordinateWatcher implements IGeoPositionWatcher using a GeoCoordinate location type. Our fake watcher will implement the same interface and will be interchangeable with GeoCoordinateWatcher.

Add the necessary methods, events, and properties to implement the interface:

public class FakeGeoCoordinateWatcher :
IGeoPositionWatcher<GeoCoordinate>
{
public void Start()
{
throw new NotImplementedException();
}
public void Start(bool suppressPermissionPrompt)
{
throw new NotImplementedException();
}
public bool TryStart(boolsuppressPermissionPrompt, TimeSpan timeou
{
throw new NotImplementedException();
}
public void Stop()
{
throw new NotImplementedException();
}
public GeoPosition<GeoCoordinate> Position
{
get { throw new NotImplementedException(); }
private set { throw new NotImplementedException(); }
}
public GeoPositionStatus Status
{
get { throw new NotImplementedException(); }
private set { throw new NotImplementedException(); }
}
publiceventEventHandler<GeoPositionChangedEventArgs
<GeoCoordinate>>PositionChanged;
bliceventEventHandler<GeoPositionStatusChangedEventArgs>StatusChanged;
}

This satisfies the interface, but as you can see, it will only throw exceptions. There are many ways you could implement this. We will go with a very simple implementation that will allow us to start testing in the emulator quickly.

How to do it...

Our fake implementation will have a static set of coordinates that will be used to call the PositionChanged event at three second intervals:

  1. First let's define our coordinates. For this we will use the coordinates from a small section of the Boston marathon, but any coordinates will do. Create a private List<GeoCoordinate> field variable named _coordinates and assign the coordinates as follows:

    List<GeoCoordinate> _coordinates = newList<GeoCoordinate>
    {
    newGeoCoordinate { Latitude = 42.248054, Longitude = -71.47439,
    Altitude = 88.1, Course = 51, Speed = 2 },
    newGeoCoordinate { Latitude = 42.248697, Longitude = -71.473961,
    Altitude = 86.2, Course = 52, Speed = 4 },
    newGeoCoordinate { Latitude = 42.249362, Longitude = -71.473403,
    Altitude = 83.3, Course = 53, Speed = 3 },
    newGeoCoordinate { Latitude = 42.24977, Longitude = -71.473017,
    Altitude = 81.4, Course = 52, Speed = 5 },
    newGeoCoordinate { Latitude = 42.250221, Longitude = -71.472695,
    Altitude = 80.9, Course = 51, Speed = 2 },
    newGeoCoordinate { Latitude = 42.250671, Longitude = -71.472394,
    Altitude = 80.4, Course = 52, Speed = 4 },
    newGeoCoordinate { Latitude = 42.251079, Longitude = -71.472094,
    Altitude = 80.9, Course = 53, Speed = 3 },
    newGeoCoordinate { Latitude = 42.25153, Longitude = -71.471815,
    Altitude = 81.8, Course = 52, Speed = 2 },
    newGeoCoordinate { Latitude = 42.251959, Longitude = -71.47145,
    Altitude = 84.3, Course = 51, Speed = 3 },
    newGeoCoordinate { Latitude = 42.252023, Longitude = -71.471386,
    Altitude = 84.7, Course = 52, Speed = 4 },
    newGeoCoordinate { Latitude = 42.252431, Longitude = -71.470807,
    Altitude = 82.8, Course = 53, Speed = 3 },
    newGeoCoordinate { Latitude = 42.252903, Longitude = -71.47012,
    Altitude = 79.4, Course = 52, Speed = 2 }
    };

  2. We will also need a timer to fire the PositionChanged event and pass the new coordinate. Declare another private field variable of type System.Threading.Timer named _timer:

    Timer named _timer:
    privateTimer _timer;

    The Start method is where all the logic begins.

  3. First we will check to see if the timer is set. If it is not null, the timer has already been set and is ticking, so we just return out of the method because there is nothing to do. Otherwise, we will create the timer and set its properties in the constructor.
  4. Set the first constructor parameter to MyTimerCallback for now and we will create it in a moment.
  5. Set the second parameter to null.
  6. Set the third parameter to a TimeSpan of 3 seconds.
  7. Set the last parameter to a Timespan of -1 millisecond to disable it.
  8. Finally we will set the Status to Initializing if it is not already set to Ready:

    public void Start()
    {
    if (_timer != null)
    return;
    _timer = new Timer(MyTimerCallback, null,
    new TimeSpan(0,0,3), new TimeSpan(-1));
    if (Status != GeoPositionStatus.Ready)
    Status = GeoPositionStatus.Initializing;
    }

  9. Create the timer callback method which will return void and take an object parameter. In this method, find the next coordinate and update the Position property. The next coordinate will either be the first coordinate, if the Position has never been set, or the next coordinate in the list.
  10. Find the current Position in the static list of coordinates and then use some LINQ methods (make sure to add System.Linq using the directive at the top of the page) to skip to the next coordinate.
  11. Set the Status to Ready to mimic the real GeoCoordinateWatcher functionality and set the Position property to a new GeoPosition<GeoCoordinate> with the timestamp set to the current DateTime and the coordinate set to the next coordinate in the constructor.
  12. Finally, reset the timer for the next coordinate. The simplest way to do this is to call Dispose on the timer, set it to null, and call the Start method again. This will reuse the code we have already written to create and reconfigure the timer:

    private void MyTimerCallback(object state)
    {
    GeoCoordinate position;
    if (Position == null)
    position = _coordinates.First();
    else
    {
    var index = _coordinates.IndexOf(Position.Location);
    position = _coordinates.Skip(index+1).FirstOrDefault() ?? _
    coordinates.First();
    }
    Status = GeoPositionStatus.Ready;
    Position = newGeoPosition<GeoCoordinate>
    (DateTime.Now, position);
    _timer.Dispose();
    _timer = null;
    Start();
    }

  13. For the other two start methods, we can simply call the first. In more complex scenarios, you can provide further logic where necessary:

    public void Start(boolsuppressPermissionPrompt)
    {
    Start();
    }
    public bool TryStart(boolsuppressPermissionPrompt,
    TimeSpan timeout)
    {
    Start();
    Return true;
    }

  14. All that is needed for the Stop method is to dispose the timer variable and set it to null. The timer controls when PositionChanged will be called again, so setting it to null will stop any further events from firing:

    public void Stop()
    {
    _timer.Dispose();
    _timer = null;
    }

  15. That handles all the methods. Now that we have the Position and Status properties, we will use the setter of these properties to call the PositionChanged and StatusChanged event respectively. Both properties will need backing private fields which will be returned from the getters. The setters will set the backing field and then call the events.
  16. There may be cases where the handlers aren't set before calling start, so we should make sure they aren't null before calling them as well:

    private GeoPosition<GeoCoordinate> _position;
    public GeoPosition<GeoCoordinate> Position
    {
    get { return _position; }
    private set
    {
    _position = value;
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
    if (PositionChanged != null)
    PositionChanged(this, new GeoPositionChanged
    EventArgs<GeoCoordinate>(value));
    });
    }
    }
    private GeoPositionStatus _status;
    public GeoPositionStatus Status
    {
    get { return _status; }
    private set
    {
    _status = value;
    Deployment.Current.Dispatcher.BeginInvoke(
    () =>
    {
    if (StatusChanged != null)
    StatusChanged(this, new GeoPositionStatusChanged
    EventArgs(value));
    });
    }
    }

How it works...

Location services utilize a background thread for getting location information. This allows the UI to continue to be responsive while waiting on the next position. The GeoCoordinateWatcher, however, fires the PositionChanged and StatusChanged events on the UI thread to help developers avoid further complexity when working with Location Services. We have done the same in our FakeGeoCoordinateWatcher by wrapping the event calls with a BeginInvoke method on the UI thread.

If we do not fire the events on the UI thread, the sample application would throw the following error when using the FakeGeoCoordinateWatcher:

Windows Phone 7 Silverlight: Location Services

Now that we have finished with our fake watcher, we can update the sample application to use it, but we should not just replace the GeoCoordinateWatcher. It would be nice to avoid having to make a manual change every time we want to switch from the fake to the real watcher. First, let's change the declaration of our _watcher variable from:

GeoCoordinateWatcher _watcher;

to:

IGeoPositionWatcher<GeoCoordinate> _watcher;

This will allow us to use either a real GeoCoordinateWatcher or our fake implementation since they both implement the IGeoPositionWatcher<GeoCoordinate> interface. In most cases, we only need to use the fake watcher when in the emulator. Thankfully, we can check the Microsoft.Devices.Environment.DeviceType property and use the fake watcher if the DeviceType is Emulator:

if (Microsoft.Devices.Environment.DeviceType == DeviceType.Emulator)
_watcher = new FakeGeoCoordinateWatcher();

else
_watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.
High) { MovementThreshold = 40 };

If you rebuild the application at this point, you will notice an error while checking the watcher Permission property for a Denied status. This occurs because the IGeoPositionWatcher interface doesn't define a Permission property . The Permission property is only defined on the GeoCoordinateWatcher. To fix this, we can check that the watcher is a GeoCoordinateWatcher and then cast the _watcher variable to a GeoCoordinateWatcher and access the Permission property. This is not as clean as before, but it is a small price to pay for adding the ability to get location information in the emulator:

if (_watcher isGeoCoordinateWatcher&& ((GeoCoordinateWatcher)_
watcher).Permission == GeoPositionPermission.Denied)
tbLatitude.Text = "Please enable location services and retry";

Now whether we are running the application in the emulator or on a phone, we will get location information.

There's more...

We have successfully implemented the IGeoPositionWatcher<GeoCoordinate> interface, but only in its simplest form. We attempted to have our fake implementation mimic the GeoCoordinateWatcher in some areas, but it will not work exactly like a GeoCoordinateWatcher at all times. You should always test your applications on a device in the field to make sure location services will act as you expect them to. You will likely find use cases or issues that you did not expect during development.

In addition, there are far more complex implementations that could be developed to more closely match your needs.

Windows Phone 7 Silverlight Cookbook All the recipes you need to start creating apps and making money with this Microsoft Windows Phone 7 Silverlight book and eBook
Published: August 2011
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

 

(For more resources on this subject, see here.)

Mapping your location

One of the most common uses of location services is to display the user's position on a map. This section will review the Map control for Windows Phone 7 and how to use it to display the user's current position.

Getting ready

Create a new Windows Phone Application project in Visual Studio, open MainPage.xaml, and delete the default layout grid and its contents. Do not add any assembly references yet. Although the Map control is located in the Microsoft.Phone.Controls.Maps assembly, there is an easier way to add the assembly reference:

Windows Phone 7 Silverlight: Location Services

By dragging a Map control from the Toolbox to your page, you are also adding any of the assembly references you might need to use:

Windows Phone 7 Silverlight: Location Services

The added references include:

  • Microsoft.Phone.Controls.Maps: Mapping controls
  • System.Device: Location services
  • System.Runtime.Serialization: General serialization classes
  • System.ServiceModel: Windows Communication Foundation (WCF)

Now that we have all our references added, we can start working with the Map control. First expand the control to fill the page by deleting the width and height attributes. Notice the control is named map1.

If you run the app, you will see that the map displays the entire world by default. You will also notice a watermark that says Invalid Credentials. Sign up for a developer account. Ignore this for now.

Windows Phone 7 Silverlight: Location Services

Viewing the map in landscape mode is preferable in many cases, so let's also allow landscape mode in the page header:

SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"

Now we can rotate the phone (or emulator) to view the map in landscape mode as well:

Windows Phone 7 Silverlight: Location Services

How to do it...

For this app, we will begin tracking a location at startup:

  1. Create an IGeoPositionWatcher<GeoCoordinate> field variable and initialize it in the page constructor located in the code behind.
  2. Copy the FakeGeoCoordinateWatcher from the navigation application to this project for use when in the emulator. Use the GeoCoordinateWatcher when on a device. Then add a handler to the PositionChanged event of the watcher and create the handler method:

    private IGeoPositionWatcher<GeoCoordinate> _watcher;
    public MainPage()
    {
    InitializeComponent();
    if (Microsoft.Devices.Environment.DeviceType ==
    DeviceType.Emulator)
    _watcher = new FakeGeoCoordinateWatcher();
    else
    _watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.
    High) { MovementThreshold = 40 };

    _watcher.PositionChanged += _watcher_PositionChanged;
    }
    Private void _watcher_PositionChanged(object sender, GeoPositionCh
    angedEventArgs<GeoCoordinate> e)
    {
    }

    There are several mapping classes that we will use with the map control. The first is the Pushpin, which highlights a specific point on the map.

  3. Add a Pushpin as a child of the Map control in XAML, name it pMyLocation, and place a TextBlock inside the Pushpin with the text Me. This will be our marker on the map of the user's current position:

    <my:Map Name="map1" >
    <my:Pushpin x:Name="pMyLocation">
    <TextBlock>Me</TextBlock>
    </my:Pushpin>
    </my:Map>

  4. In the position changed handler, first make sure the location is known. If it is not, return out of the method; if it is, then set the pushpin's location to the new location. We can also have the map reposition to be centered on the user's position as it changes. Do this by setting the map's Center property to the new location:

    private void _watcher_PositionChanged(object sender, GeoPositionCh
    angedEventArgs<GeoCoordinate> e)
    {
    var location = e.Position.Location;
    if (location.IsUnknown)
    return;
    pMyLocation.Location = location;
    map1.Center = location;
    }

  5. Now, if you run the app in the emulator, you should see the Me pushpin in the Boston area moving slightly every few seconds. If you click on the map with your mouse, it will begin zooming in to more detail. We can also zoom the map by default to the street level by setting the ZoomLevel property to 18:

    <my:Map Name="map1"ZoomLevel="18">

  6. We will also add an indication of the accuracy of the position information using the HorizontalAccuracy value from the GeoCoordinate. We can use a MapLayer to add a red ellipse to the map around the user's position. Add a MapLayer node to the Map control just above the Pushpin in XAML. Give it a name so we can reference it in the code behind as well:

    <my:MapLayer Name="accuracyLayer">
    </my:MapLayer>

  7. As you will recall, the HorizontalAccuracy value is in meters. We will need to convert the value from meters to the appropriate pixel size for the map.

    Map Scale Information:
    Map Scale is the ratio of inches on the map to inches on the ground. This ratio changes at each zoom level. Microsoft provides an in-depth description of the math behind the Bings Map Tile System, including the Map Scales for each zoom level at http://msdn.microsoft.com/en-us/library/bb259689.aspx.
    At zoom level 18, the Map Scale is 1:2,257, meaning 1 inch on the map is equal to 2,257 inches on the ground. To get the correct pixel size, we first convert the accuracy value from meters to inches by multiplying the value by the number of inches in a meter (39.3700787). Next, divide the inches value by the Map Scale, 2,257. Finally we need to convert the map inches to pixels. The DPI (Dots Per Inch) for WP7 varies by phone screen size, but the estimated average DPI over all current Windows Phones on the market is 241.

  8. Multiply the map inches value by 241. The result is the radius, in pixels, for our accuracy ellipse:

    Windows Phone 7 Silverlight: Location Services

  9. Create an ellipse, by setting the Width and Height properties to the pixel radius multiplied by two to get the diameter. Also set the Fill to a SolidColorBrush that is slightly transparent so you can see the map underneath it.
  10. Unlike the standard Silverlight map control, the map control for Windows Phone does not yet have a SetPosition method . This means we will need to clear the MapLayer and recreate the ellipse on each position change:
    • Call the Clear method on the layer's Children property to remove the old ellipse
    • Call AddChild on the Children property passing the ellipse to recreate it
  11. Along with the ellipse, you will pass the user's current location and an offset for the ellipse. The offset allows us to center the ellipse around the user's location, otherwise it would be displayed below and to the right of the Pushpin. We will crea te a new point with the x and y coordinates set to the negative radius to center it properly:

    var radius = (e.Position.Location.HorizontalAccuracy * 39.3700787)
    / 2257 * 241;
    var ellipse = new Ellipse { Width = radius * 2, Height = radius *
    2, Fill = new SolidColorBrush(Color.FromArgb(125, 255, 0, 0)) };
    accuracyLayer.Children.Clear();
    accuracyLayer.AddChild(ellipse, location, new Point(-radius,
    -radius));

  12. We must also set the HorizontalAccuracy property of each GeoCoordinate in the _coordinates list in FakeGeoCoordinateWatcher to see the ellipse in the emulator. Set the HorizontalAccuracy to a value in the range from 5 to 60. Now if we deploy the app to the emulator, we can visually see the accuracy of the location information:

    Windows Phone 7 Silverlight: Location Services

How it works...

The Windows Phone Map control is very similar to the Silverlight Map control. In many cases, code for the two is interchangeable. You will find many tutorials online for the Silverlight map control, which you can use to help you implement map features in your apps.

The Map control utilizes Bing Maps for obtaining map data. You will need Bing Maps Key to use the Map control in production, but we can use the Map without it for demonstration purposes. You can obtain a key after creating a Bing Maps Developer Account at: https://www.bingmapsportal.com.

Creating an account and getting a key is a free and simple process that will take less than 15 minutes.

Summary

In this article we covered:

  • Tracking latitude and longitude
  • Tracking altitude, speed, and course
  • Saving battery by using a location wisely
  • Using location services with the emulator
  • Mapping your location

Further resources on this subject:


About the Author :


Jonathan Marbutt

At the early age of ten Jonathan began to learn to program building simple games to amuse his friends and family. Twenty years later he still has passion for technology and development. Jonathan loves to share his passion with other developers by writing for his blog and various books as well as speaking at many user groups and technology conferences. Throughout the past three years Jonathan’s focus has primarily been on working with Silverlight and its most recent version for Windows Phone 7. Through this newest version for Windows Phone 7, Jonathan has been able to work on many high profile applications that are rated as some of the most downloaded applications.

Jonathan is also currently the Vice President and Co-founder of WayCool Software, Inc. based in Birmingham, AL, which provides solutions for non-profit organizations. Jonathan also has been providing consulting services through his latest venture JM TechWare, Inc., where he helps provide both User Experience and architectural guidance on Silverlight, Windows Phone 7 and WPF applications. In addition to development, Jonathan has co-authored Visual Basic 2010 and .Net 4 published by Wrox Press.

Robb Schiefer Jr.

Robb is a follower of Christ, husband to the perfect wife and father of two beautiful girls. Coincidentally, he is also a successful .NET software developer, which is a better qualification for writing a programming book.

His development career started while working part time during college where he learned graphic design basics and built simple data-driven PHP websites. After college he worked for a small startup on a VB6 application for educators and learned .NET by jumping head first into building a complimentary ASP.NET site. Since then he has worked as a .NET developer for a market leading, privately held corporation with a global presence. This enterprise environment has provided many unique challenges and learning opportunities. He currently leads a development team in the company’s latest development efforts, mentors many developers and plays a leading role in planning the company’s .NET architecture.

Prior to the announcement of Windows Phone 7 he had little experience with Silverlight but always wanted to learn it. WP7 provided the perfect opportunity to learn Silverlight in a defined space and on a smaller scale. He currently has several apps in the marketplace and has plans for many more (if he ever gets this book done).

Books From Packt


Microsoft SharePoint 2010 Enterprise Applications on Windows Phone 7
Microsoft SharePoint 2010 Enterprise Applications on Windows Phone 7

Windows Phone 7 XNA Cookbook
Windows Phone 7 XNA Cookbook

Microsoft Silverlight 4: Building Rich Enterprise Dashboards
Microsoft Silverlight 4: Building Rich Enterprise Dashboards

Microsoft Visual Studio LightSwitch Business Application Development
Microsoft Visual Studio LightSwitch Business Application Development

Microsoft Silverlight 4 and Windows Azure Enterprise Integration: RAW
Microsoft Silverlight 4 and Windows Azure Enterprise Integration: RAW

Microsoft Silverlight 4 Data and Services Cookbook: LITE
Microsoft Silverlight 4 Data and Services Cookbook: LITE

BlackBerry Enterprise Server 5 Implementation Guide
BlackBerry Enterprise Server 5 Implementation Guide

Google Apps: Mastering Integration and Customization
Google Apps: Mastering Integration and Customization


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software