Chapter 15. Geographic Location


In This Chapter

Understanding sensing technologies

Monitoring position changes

Simulating position changes with the emulator’s location simulator

Simulating position changes with a device compatible code driven approach

Detecting whether your app is executing within the emulator

Modulating position change events with Rx


Geographic location has become one of the principal uses of smartphones, and for good reason. Smartphones are portable, they go with us nearly everywhere, and offer the kind of processing power required to make displaying and interacting with complex maps possible.

The Windows Phone SDK provides an API that makes working with geographic location straightforward. The geographic location APIs on the phone abstract the location-sensing technologies and unburden the developer from having to know anything about the specific hardware in the device. Your app can specify a desired accuracy, either low or high, and the phone does the rest; it decides what location sensing components to use.

The .NET location API is consistent across platforms and can be used in much the same way in a phone app as it can in a desktop CLR application.

This chapter begins with an overview of the location sensing technologies available on Windows Phone and then dives into the location API. The chapter looks at monitoring location changes and at simulating location changes using both the emulator’s location simulator and a custom class compatible with unit testing and testing on a physical device.

Finally, the chapter delves into the Reactive Extensions (Rx) and looks at sampling events to make rapidly firing position changes manageable.

Location Sensing Technologies

Windows Phone uses the following strategies to determine its geographic location:

• A-GPS

• Wi-Fi triangulation

• Cell tower triangulation

Each strategy has advantages and disadvantages, depending on environmental factors and power efficiency requirements (see Figure 15.1). The following sections discuss the pros and cons of each technology.

Image

Figure 15.1. Sensor technologies available to Windows Phone


Note

The term accuracy, used frequently in this chapter, describes how close a location reading is to the true physical location.


A-GPS

A-GPS (Assisted Global Positioning System) is a satellite-based positioning system, which is generally the most accurate of the geo location methods available on the phone. The accuracy afforded by A-GPS, however, comes with a power penalty. It uses significantly more power than the other two sensing technologies. Because of this, it should only be used when an application needs a high level of accuracy.

GPS uses radio signals from satellites and does not perform as well in dense urban areas. In poor signal conditions, signals may suffer multipath errors; signals bounce off buildings, or are weakened by passing through tree cover. Later in the chapter, you see how to reduce the effect of multipath errors by applying a movement threshold to signal readings.

A-GPS is an extension to GPS. It improves GPS’s startup performance by using an assistance server to supply satellite orbital information to the device over the network. This is faster than acquiring the information from the satellite itself, which can also prove difficult in low signal conditions.

Cell Tower Triangulation

Cell tower location uses triangulation against multiple cell towers to pinpoint the phone’s location based on the ping time from each tower. The distance from a single cell tower can be roughly determined by the time it takes for a phone to respond to a ping. The location of the cell towers, combined with their ping times, allows the phone’s location to be pinpointed (see Figure 15.2).

Image

Figure 15.2. A phone’s location can be pinpointed using cell tower triangulation.

Accuracy increases as more towers are included in the calculation.

While cell tower triangulation uses less power than A-GPS, it is less accurate. Cell tower triangulation works best where there are more cell towers. Thus, areas such as city fringes or countryside may not provide an adequate level of accuracy. Unlike A-GPS, however, cell tower triangulation works indoors.

Wi-Fi Triangulation

Wi-Fi triangulation provides a middle ground between A-GPS and cell tower triangulation. Wi-Fi triangulation uses less power than GPS and can provide better accuracy than cell tower triangulation in some environments.

Wi-Fi location relies on a global location database of Wi-Fi networks and the ability of a phone device to detect Wi-Fi networks within its vicinity.

Wi-Fi triangulation works by detecting networks that are in range, measuring the signal strength of each network, and then triangulating the result using the Wi-Fi network location database.


Note

The Wi-Fi network location database is provided by the company Navizon, and there is currently no public API for accessing the data directly.


Geographic Location Architecture

The Windows Phone location architecture consists of three layers: a hardware layer, a native code layer, and a managed layer (see Figure 15.3).

Image

Figure 15.3. Location architecture layers

The hardware layer comprises the location related hardware components in the phone device, which includes a GPS receiver, a Wi-Fi interface, and a cellular radio. Each serves as a provider of location information with varying degrees of accuracy and power consumption.

The second layer, a native code layer, communicates directly with the hardware devices to determine the location of the device. The choice of hardware component, or combination of components, depends on the accuracy level requested by the app and the availability of data; there may not be any Wi-Fi networks in the vicinity for example.

The native code layer also uses a Microsoft web service to provide location related information from a database.

Code cannot be written to directly interact with the native layer, nor the hardware layer. For this, a managed code layer exists, which provides a hardware agnostic API. Recall from Chapter 2, “Fundamental Concepts in Silverlight Development for Windows Phone,” that P/Invoke is not allowed at all on the phone. All interoperation with location components on the phone must therefore be done via the managed layer.

Getting Started with Location

The CLR types pertaining to geographic location reside in the System.Device assembly. To use geographic location in your Windows Phone project, add a reference to the System.Device assembly (see Figure 15.4).

Image

Figure 15.4. Adding a reference to the System.Device assembly

The geographic location types are located in the namespace System.Device.Location. Of these types, the GeoCoordinateWatcher class is discussed in detail in the next section.

GeoCoordinateWatcher Class

The GeoCoordinateWatcher class can be used by your app to monitor the location of the phone. It contains events for hardware state changes and geographic coordinate changes.

When instantiating a GeoCoordinateWatcher, you request a desired accuracy level, which can be either the default accuracy level, which lets the OS decide the accuracy level, or a high accuracy level, which causes the OS to prefer A-GPS. See the following example:

GeoCoordinateWatcher defaultAccuracyWatcher = new GeoCoordinateWatcher();
GeoCoordinateWatcher highAccuracyWatcher
    = new GeoCoordinateWatcher(GeoPositionAccuracy.High);

The GeoPositionAccuracy enum has the following two values:

Default—Results in a power optimized (or low power) configuration at the probable cost of decreased accuracy. Moreover, the phone device favors the use of cell towers triangulation and Wi-Fi triangulation above GPS.

High—Favors GPS when available at the cost of greater power usage and, thus, reduced battery life.

DesiredAccuracy Property

This property retrieves the desired accuracy level specified when the GeoCoordinateWatcher was instantiated. It has no setter.


Best Practice

Use the default (power optimized) accuracy setting unless a higher level of accuracy is required. Using lower accuracy minimizes power consumption, thereby increasing battery life.


MovementThreshold Property

In some environments, signal noise can cause a GPS device to register movement when there is no movement. This can be caused by surface reflection, which occurs when signals bounce off walls or other structures, or by other environmental factors. It is also exacerbated by the absence of a GPS antenna on Windows Phone devices.

The MovementThreshold property of the GeoCoordinateWatcher allows you to specify the minimum amount of movement required to register a change in position. When a change is registered the GeoCoordinateWatcher’s PositionChanged event is raised.

MovementThreshold is a double value, which indicates the distance in meters, relative to the last coordinate received. The MovementThreshold property allows position change notifications to be smoothed. If the value is set too low, the PositionChanged event may be raised too frequently, or when there is no actual change in location. Conversely, setting the value too high reduces the ability of the device to provide up-to-date location coordinates. A road navigation app, for example, with a MovementThreshold value set too high, may cause a user to miss a turn. Set too low, the application may provide continuous directions when the phone is, in fact, stationary.


Tip

Setting the GeoCoordinateWatcher.MovementThreshold property to at least 20 helps to smooth out the signal, causing the PositionChanged event to be raised only when the location has changed significantly.


Monitoring Position Changes

The GeoCoordinateWatcher class exposes the following three events:

PositionChanged—This event is raised when a change in location is detected.

StatusChanged—This event is raised when the underlying location service changes state; for example, when the location component is receiving data or when it has been disabled.

PropertyChanged—This event is raised when a property changes and satisfies the INotifyPropertyChanged implementation.


Note

The GeoCoordinateWatcher.PropertyChanged event is only raised for the Status and Position properties. Changing the MovementThreshold property, for example, does not raise the PropertyChanged event.


PositionChanged Event

The PositionChanged event is raised whenever a significant change is detected in the phone’s location. How large the change needs to be is determined by the GeoCoordinateWatcher object’s MovementThreshold property.

The following code fragment demonstrates how to subscribe to the PositionChanged event:

geoPositionWatcher.PositionChanged += HandleGeoPositionWatchPositionChanged;

When the PositionChanged event is raised, the handler receives a GeoPositionChangedEventArgs<GeoCoordinate> object that contains a GeoPosition<GeoCoordinate> property, as shown:

void HandleGeoPositionWatchPositionChanged(
    object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
    GeoPosition<GeoCoordinate> position = e.Position;
    GeoCoordinate location = position.Location;
    DateTimeOffset timestamp = position.Timestamp;
}

In the context of the Geo Location API, a position can be thought of as a location at a point in time. The Position property of the GeoPositionChangedEventArgs<GeoCoordinate> is a GeoPosition instance containing the following two properties:

Location (of type GeoCoordinate)

Timestamp (of type DateTimeOffset)

StatusChanged Event

The status of the underlying geographic location provided is indicated by the GeoCoordinateWatcher object’s Status property, which is of type GeoPositionStatus. GeoPositionStatus is an enum that contains the following four values:

Disabled—The location provider is disabled. No position updates occur.

Initializing—The location provider is initializing. This status occurs, for example, when the A-GPS component is obtaining a fix.

NoData—No location data is available from any location provider.

Ready—A location provider is ready to supply new data. Once in this state, the GeoCoordinateWatcher is able to raise its PositionChanged event.

The location provider transitions between these states as depicted in Figure 15.5.

Image

Figure 15.5. GeoCoordinateWatcher.Status property transitions between GeoPositionStatus values.

The following example demonstrates how to subscribe to the StatusChanged event:

geoPositionWatcher.StatusChanged += HandleGeoPositionWatcherStatusChanged;

When the StatusChanged event is raised, the handler receives a GeoPositionStatusChangedEventArgs object, as shown in the following excerpt:

void HandleGeoPositionWatcherStatusChanged(
    object o, GeoPositionStatusChangedEventArgs args)
{
    GeoPositionStatus status = args.Status;
    switch (status)
    {
            case GeoPositionStatus.Disabled:
            // ...
            break;
            case GeoPositionStatus.Initializing:
            // ...
            break;
            case GeoPositionStatus.NoData:
            // ...
            break;
            case GeoPositionStatus.Ready:
            // ...
            break;
    }
}

Starting the GeoCoordinateWatcher

When you instantiate the GeoCoordinateWatcher it does not start monitoring the location provider until its Start method is called, as shown in the following excerpt:

geoPositionWatcher.Start();


Best Practice

GeoCoordinateWatcher leverages phone hardware that consumes power. Only start a GeoCoordinateWatcher when it is needed by your app, and stop it as soon as it is no longer needed. This reduces power consumption and increases the battery life of the phone.


The Start method has an overload that accepts a Boolean parameter named suppressPermissionPrompt. This parameter, however, is designed to be used by Silverlight for the Browser and is unused in the Windows Phone implementation.

Similarly, the Windows Phone implementation of the GeoCoordinateWatcher.TryStart method does not use either the suppressPermissionPrompt parameter or the timeout parameter. Its method name is misleading in its current implementation since it returns true if the GeoCoordinateWatcher is already started and false otherwise, even if it is successfully started during the method call. For this reason, it is recommended that you do not use the method.

Testing Apps That Use the GeoCoordinateWatcher

The Windows Phone emulator includes a tool for simulating location tracking, which can be accessed by clicking the Additional Tools button on the emulator menu (this was shown in Figure 14.2), and then selecting the Location tab on the Additional Tools window.

The location simulator works in two modes: a live mode that allows you to interact with the map to send location data to the emulator as soon as you click on the map and a playback mode that causes the location simulator to step through a sequence of map points, which you enter by clicking on the map (see Figure 15.6).

Image

Figure 15.6. Location simulator

Map points can be saved to a file and loaded again at a later time.


Note

The Play button is enabled only when the Live button is toggled off.


When using the location simulator, any GeoCoordinateWatcher instances in your app respond as though they are receiving actual location data, giving you the opportunity to test your app’s geo location features.

Code Driven Location Simulation

The Windows Phone emulator’s location simulator is useful for testing your app while it is executing within the emulator. To test the geo location features of your app on a physical device or within a unit test requires a different, code driven approach.

For this you must supplant the GeoCoordinateWatcher object with your own implementation of the IGeoPositionWatcher<GeoCoordinate> interface.

Included in the downloadable sample code is a class called MockGeoCoordinateWatcher, which does just that. It works like the GeoCoordinateWatcher but allows you to supply a list of GeoCoordinate values, which are analogous to the location simulator’s map points (see Listing 15.1).

MockGeoCoordinateWatcher implements INotifyPropertyChanged, just like the built-in GeoCoordinateWatcher class.

In addition to the properties of the IGeoCoordinateWatcher class, MockGeoCoordinateWatcher has the following three properties that allow you to tailor when the PositionChanged event is raised:

InitializationMs—The time, in milliseconds, before the watcher’s Status transitions to the NoData state

FixDelayMs—The time before the watcher’s Status transitions from the NoData state to the Ready state

PositionChangedDelayMs—The time interval between PositionChanged events

MockGeoCoordinateWatcher allows you to add waypoints to a list of GeoCoordinate values using its WayPoints property.

The MockGeoCoordinateWatcher.WalkPath method runs on a background thread and processes the waypoints sequentially (see Listing 15.1).

If there are no waypoints specified, then the MockGeoCoordinateWatcher uses a default list of coordinates.

The first time WalkPath is called, the watcher simulates initialization by pausing for the duration specified by the InitializationMs value. It then updates its Status to Ready and iterates over the WayPoints collection, raising the PositionChanged event for each one.

Listing 15.1. MockGeoCoordinateWatcher.WalkPath Method


void WalkPath()
{
    if (wayPoints.Count < 1)
    {
        List<GeoCoordinate> defaultWayPoints = GetDefaultCoordinates();
        wayPoints.AddRange(defaultWayPoints);
    }

    if (firstRun)
    {
        Status = GeoPositionStatus.Initializing;
        Wait(InitializationMs);
    }

    Status = GeoPositionStatus.NoData;
    Wait(FixDelayMs);

    GeoCoordinate coordinate = wayPoints.First();

    if (firstRun)
    {
        Position = new GeoPosition<GeoCoordinate>(
                            DateTimeOffset.Now, coordinate);
        firstRun = false;
    }
    Status = GeoPositionStatus.Ready;
    Wait(PositionChangeDelayMs);
    int index = 1;

    while (started)
    {
        if (wayPoints != null && wayPoints.Count > index)
        {
            coordinate = wayPoints[index++];
        }
        SetPosition(coordinate);
        Wait(PositionChangeDelayMs);
    }
}


The Wait method uses the Monitor to block the background thread for the specified number of milliseconds (see Listing 15.2).


Tip

Using the Monitor to block the thread is preferable to calling Thread.Sleep because, unlike Monitor.Wait, Thread.Sleep cannot be unblocked.


Listing 15.2. Wait Method


static readonly object waitLock = new object();

static void Wait(int delayMs)
{
    if (delayMs > 0)
    {
        lock (waitLock)
        {
            Monitor.Wait(waitLock, delayMs);
        }
    }
}


Both MockGeoCoordinateWatcher and the built-in GeoCoordinateWatcher implement IGeoPositionWatcher<GeoCoordinate>. This allows us to use either type without referring to a concrete implementation. This is demonstrated in the next section.

A Walkthrough of the Position Viewer Sample

The sample for this chapter includes a set of classes for monitoring location using either a GeoCoordinateWatcher or a MockGeoCoordinateWatcher. The UI includes three input controls: a start and a stop Button to toggle monitoring location changes on and off and a Slider.

The sample code for this section is located in the GeographicLocation directory of the WindowsPhone7Unleashed.Examples project.

GeoPositionViewModel Class

The GeoPositionViewModel.Start method instantiates either a mock or a built-in IGeoPositionWatcher<GeoCoordinate> object (see Listing 15.3).


Tip

Rather than requiring the viewmodel to determine which IGeoPositionWatcher<GeoCoordinate> implementation it should use, Inversion of Control (IoC) could also be employed to resolve the object. IoC is discussed in Chapter 22, “Unit Testing.”


The Start method subscribes to the PositionChanged and StatusChanged events of the watcher, and then calls the watcher’s Start method to begin receiving location notifications.

When the PositionChanged event is raised, the viewmodel’s GeoCoordinate property is set to the current location.

Listing 15.3. GeoPositionViewModel.Start Method (excerpt)


void Start()
{
    if (running)
    {
        return;
    }

    Running = true;
    CanStart = false;

    geoPositionWatcher = EnvironmentValues.UsingEmulator
        ? new MockGeoCoordinateWatcher()
        : (IGeoPositionWatcher<GeoCoordinate>)new GeoCoordinateWatcher
                                                {MovementThreshold = 20};

    geoPositionWatcher.PositionChanged
        += (o, args) =>
            {
                GeoCoordinate = args.Position.Location;
                ResolveAddress(args.Position.Location);
            };

    geoPositionWatcher.StatusChanged
        += (o, args) => GeoPositionStatus = args.Status;

    geoPositionWatcher.Start();
}


Determining whether the application is running within the emulator is done using the custom static EnvironmentValues.UsingEmulator property, shown in the following excerpt:

static bool? usingEmulator;

public static bool UsingEmulator
{
    get
    {
        if (!usingEmulator.HasValue)
        {
            usingEmulator = Environment.DeviceType == DeviceType.Emulator;
        }
        return usingEmulator.Value;
    }
}

The viewmodel contains two DelegateCommands that are used to start and stop position monitoring. The commands are initialized in the viewmodel constructor, as shown:

public GeoPositionViewModel()
{
    startCommand = new DelegateCommand(obj => Start(), arg => CanStart);
    stopCommand = new DelegateCommand(obj => Stop(), arg => Running);
    PropertyChanged += delegate { RefreshCommands(); };
}

Tapping the Stop button causes the viewmodel’s StopCommand to execute, which calls the Stop method of the geoPositionWatcher. This prevents the PositionChanged event from being raised. The Stop method is shown in the following excerpt:

void Stop()
{
    if (!running || geoPositionWatcher == null)
    {
        return;
    }

    if (sampler != null)
    {
        sampler.Dispose();
    }

    geoPositionWatcher.Stop();
    Running = false;
    CanStart = true;
}

Displaying Location Using the GeoPositionView Page

The view has two buttons, which are bound to the viewmodel’s StartCommand and StopCommand (see Listing 15.4).

The GeoPositionView contains several TextBlock elements that are bound to viewmodel properties.

A Slider is bound to the viewmodel’s SampleIntervalMs property, allowing the user to control the sample frequency. The sampling feature is explored later in the chapter.

Listing 15.4. GeoPositionView Page (excerpt showing main content panel)


<StackPanel Grid.Row="1" Margin="21,30,21,0">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="80" />
            <RowDefinition Height="80" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <TextBlock Text="Latitude"
                   Style="{StaticResource LatLongTitleStyle}" Margin="20"/>
        <TextBlock Text="{Binding GeoCoordinate.Latitude}"
                   Style="{StaticResource LatLongStyle}" Grid.Row="1"/>
        <TextBlock Text="Longitude"
                   Style="{StaticResource LatLongTitleStyle}"
                   Grid.Column="1" Margin="20" />
        <TextBlock Text="{Binding GeoCoordinate.Longitude}"
                   Style="{StaticResource LatLongStyle}"
                   Grid.Row="1" Grid.Column="1" />
    </Grid>
    <TextBlock Text="Status: "
               Style="{StaticResource PhoneTextTitle3Style}" />
    <TextBlock Text="{Binding GeoPositionStatus}"
               Style="{StaticResource PhoneTextNormalStyle}"
               Foreground="{StaticResource PhoneAccentBrush}"
               Height="50" />
    <TextBlock>Sample interval in milliseconds (0 for realtime):</TextBlock>
    <Slider Minimum="0"
            Maximum="10000"
            Value="{Binding SampleIntervalMs, Mode=TwoWay}"
            IsEnabled="{Binding CanStart}"
            LargeChange="1000" SmallChange="1000"/>
    <TextBlock Text="{Binding SampleIntervalMs}"
               Width="100" Height="50" HorizontalAlignment="Left"
               Style="{StaticResource PhoneTextNormalStyle}" />
    <TextBlock Text="Message"
               Style="{StaticResource PhoneTextTitle3Style}" />
    <TextBlock Text="{Binding Message}" Height="50"
               Style="{StaticResource PhoneTextNormalStyle}" />
    <StackPanel Orientation="Horizontal">
        <Button Content="Start"
                Command="{Binding StartCommand}"
                Margin="0,20,0,0" Width="150" HorizontalAlignment="Left" />
        <Button Content="Stop"
                Command="{Binding StopCommand}"
                Margin="0,20,0,0" Width="150" HorizontalAlignment="Left" />
    </StackPanel>
</StackPanel>


Tapping the Start button causes the current location to be displayed whenever the IGeoCoordinateWatcher object’s PositionChanged event is raised (see Figure 15.7).

Image

Figure 15.7. GeoPositionView page

Civic Address Resolution

CivicAddressResolver is supposed to be able to resolve a street address from a geographic coordinate. It has not, however, been fully implemented yet and is unable to resolve an address.


Note

In the 7.5 release of the Windows Phone OS the CivicAddressResolver class has not been fully implemented.


In Chapter 16, “Bing Maps,” you see how to use the Bing Maps Geocode Service to resolve an address from a geo coordinate, and vice versa.

The GeoPositionViewModel class demonstrates how to use the CivicAddressResolver, but the CivicAddressResolver.ResolveAddressCompleted handler always receives an unknown address value.

Sampling the PositionChanged Event with Rx

If the GeoCoordinateWatcher class is assigned too low a MovementThreshold value, the event handler for the GeoCoordinateWatcher.PositionChanged event may be raised more frequently than needed, which can potentially degrade the performance of your app. The Reactive Extensions (Rx) can eliminate this issue by modulating, or sampling, the event, restricting calls to the PositionChanged event handler to a certain number each second.

Rx is a managed library that has been included as part of the Windows Phone Framework Class Library (FCL). At its essence, Rx allows events to be treated as a data stream. Rx allows you to subscribe to this data stream, and to manipulate and filter the data stream before it is handled by your app.

Rx can be thought of as LINQ to Events, and includes all the standard LINQ query operators. Rx provides a convenient mechanism for passing around event streams, which makes working with events easier. It uses the Dispose pattern and the IDisposable interface in conjunction with event subscriptions, and automatically unsubscribes from events, eliminating potential memory leaks.

If you are new to Rx, it is recommended that you learn more about it because it is an invaluable tool when working with an asynchronous programming model. Rx is being used evermore frequently by developers for asynchronous programming. The Silverlight Toolkit team, for example, used Rx to write reliable, event-based asynchronous tests for many of the Toolkit components.

Getting Started with Rx for Windows Phone

To use Rx in your app add a reference to the System.Observable and the Microsoft.Phone.Reactive assemblies (see Figure 15.8).

Image

Figure 15.8. Add a reference to the System.Observable and Microsoft.Phone.Reactive assemblies.

Rx types reside in the Microsoft.Phone.Reactive namespace.

The key to using Rx is to understand the two principal interfaces: IObserver and IObservable. Rx’s IObservable and IObserver are analogous to IEnumerable and IEnumerator, except for one key difference: They do not block.

Rx allows you to create an IObservable object from an event. In the following excerpt, an IObservable is created using the GeoPositionWatcher<GeoCoordinate> instance’s PositionChanged event:

IObservable<IEvent<GeoPositionChangedEventArgs<T>>> observable
    = Observable.FromEvent<GeoPositionChangedEventArgs<T>>(
        ev => watcher.PositionChanged += ev,
        ev => watcher.PositionChanged -= ev);

By supplying both an add handler and a remove handler, the PositionChanged event is automatically unsubscribed once the observable instance is disposed.

Once an IObservable has been acquired, you can limit the frequency of event notifications by using the Sample extension method for the IObservable<T> type, as shown in the following excerpt:

IObservable<IEvent<GeoPositionChangedEventArgs<T>>> sampled
    = observable.Sample(TimeSpan.FromMilliseconds(sampleIntervalMs));

The final step is to subscribe to the data stream. This is done by calling the Subscribe extension method, passing it a delegate. The following excerpt calls the Subscribe method and passes a lambda expression for the PositionChanged event handler:

sampled.Subscribe(
    args =>
        {
            GeoPosition<T> position = args.EventArgs.Position;
            Location = position.Location;
            TimeStamp = position.Timestamp;
        });

If the sampleIntervalMs value is equal to 1000, the PostionChanged event handler is called, at most, once each second.


Caution

Using the Subscribe method results in an object that implements IDisposable. It is critical that the Dispose method be called on the object when it is no longer needed, as not doing so can lead to a memory leak. The event source (in this case the GeoPositionWatcher) may keep the target (GeoPositionSampler) alive.


The downloadable sample code contains a class called GeoPositionSampler that encapsulates this sampling functionality into a reusable class (see Listing 15.5).

GeoPositionSampler wraps a GeoCoordinateWatcher instance and allows you to sample the IGeoPositionWatcher<T>.PositionChanged event. It implements INotifyPropertyChanged so that a UI element can bind to its Location property if needed.

GeoPositionSampler implements IDisposable so that when it is disposed, it disposes its Rx subscription object and unsubscribes from the watcher’s StatusChanged event.

Listing 15.5. GeoPositionSampler<T> Class (excerpt)


public class GeoPositionSampler<T> : NotifyPropertyChangeBase, IDisposable
{
    readonly IDisposable subscription;
    readonly IGeoPositionWatcher<T> watcher;

    public GeoPositionSampler(
        IGeoPositionWatcher<T> watcher, int sampleIntervalMs)
    {
        this.watcher = ArgumentValidator.AssertNotNull(watcher, "watcher");

        IObservable<IEvent<GeoPositionChangedEventArgs<T>>> observable
            = Observable.FromEvent<GeoPositionChangedEventArgs<T>>(
                ev => watcher.PositionChanged += ev,
                ev => watcher.PositionChanged -= ev);

        IObservable<IEvent<GeoPositionChangedEventArgs<T>>> sampled
            = observable.Sample(
                TimeSpan.FromMilliseconds(sampleIntervalMs));

        subscription = sampled.Subscribe(
            args =>
                {
                    GeoPosition<T> position = args.EventArgs.Position;
                    Location = position.Location;
                    TimeStamp = position.Timestamp;
                });

        watcher.StatusChanged += WatcherOnStatusChanged;
    }

    void WatcherOnStatusChanged(
        object o, GeoPositionStatusChangedEventArgs args)
    {
        Status = args.Status;
    }

    T location;

    public T Location
    {
        get
        {
            return location;
        }
        private set
        {
            Assign("Location", ref location, value);
        }
    }

    DateTimeOffset timeStamp;

    public DateTimeOffset TimeStamp
    {
        get
        {
            return timeStamp;
        }
        private set
        {
            Assign("TimeStamp", ref timeStamp, value);
        }
    }

    GeoPositionStatus status;

    public GeoPositionStatus Status
    {
        get
        {
            return status;
        }
        set
        {
            Assign("Status", ref status, value);
        }
    }
...
}


The GeoPositionSampler initialization has been incorporated into the GeoPositionViewModel.Start method, as shown:

if (sampleIntervalMs > 0)
{
    sampler = new GeoPositionSampler<GeoCoordinate>(
                        geoPositionWatcher, sampleIntervalMs);

    sampler.PropertyChanged += (o, args) =>
                                {
                                    ResolveAddress(sampler.Location);
                                    GeoCoordinate = sampler.Location;
                                    GeoPositionStatus = sampler.Status;
                                };
}

The GeoPositionSampler constructor accepts an IGeoPositionWatcher<T>, which is normally a GeoCoordinateWatcher, and a sample interval in milliseconds.

The GeoPositionView page uses a Slider control to set the sample interval of the GeoPositionSampler (see Figure 15.9).

Image

Figure 15.9. A Slider control is used to set the sample interval.

Increasing the sample interval causes the latitude and longitude display values to be updated less frequently.

Sampling the PositionChanged event allows your app to respond periodically to location changes and can improve the behavior of your app when the GeoCoordinateWatcher object’s movement threshold is set too low for the current conditions.

Summary

This chapter looked at the various location sensing technologies, and how they compare in terms of power usage and accuracy. The chapter also explored how to monitor position changes using the GeoCoordinateWatcher class, and how to simulate position changes using both the emulator’s location simulator and a custom class that is compatible with unit testing and testing on a physical device.

Finally, the chapter demonstrated how Rx can be used to modulate, or restrict, the frequency of PositionChanged event handler calls.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.115.131