Rich & I have been working on app which makes heavy use of geo-location information provided by the GeoCoordinateWatcher class on the WP7 platform. As the documentation on MSDN states this class exposes the Windows Phone location services. The GeoCoordinateWatcher class implements the INotifyPropertyChanged interface and all the events generated will be fired on the UI thread allowing you to easily bind the class to the UI, but as we already knew this is not always a good idea - for a detailed reason why this can be a bad idea check out this post by Jaime Rodriguez.
What we also discovered is that you can't always trust the first position event generated by this class.
We have a requirement to get the current location when a user clicks a button, the user could do this at any time when using the application - they could click once, a dozen times or not at all. Every time they click the button we need to get an accurate single value. The accuracy of the value depend on both the actual value and the age of the value. Every value generated by the GeoCoordinateWatcher class is published by via the PositionChanged event and this is made up of 2 components - the location value and the timestamp when the location value was generated, this is exposed as the GeoPosition<T> class.
The PositionChanged event is defined as follows. I've included the StatusChanged event to show all the event signatures for the class:
public class GeoCoordinateWatcher : IDisposable, INotifyPropertyChanged, IGeoPositionWatcher<GeoCoordinate>
The point of interest is the type used with the event handler. The GeoPositionChangedEventArgsclass has the following definition.The Position property exposing the GeoPosition<T> value, this has the time stamp value.
{
We use the following code to return a single geo-location value when the button is clicked. It uses Rx (reactive extensions) to wrap up the call to the GeoCoordinateWatcher class. We don't just use the 'FromEventPattern' Rx method because we don't want the instance of the GeoCoordinateWatcher running riot and generating a gazillion location results and killing the UI thread!
The highlighted areas show how I am using the time offset to determine the validity of the location published by the GeoCoordinateWatcher class. In this demo instance I am saying - 'if the position timestamp is greater than 20 seconds ago then ignore the value...'. When valid the new location is published using the Rx method 'OnNext' on the returned Subject<T>. We also call the Rx 'OnCompleted' method to make the 'Finally' method execute and shut down the watcher by calling 'watcher.Stop()'.
Note: We also do the 'Start' of the GeoCoordinateWatcher on a background thread to prevent blocking of the current thread - in this case the UI thread.
This class is the used in the page class as follows:
When run for the first time (on the emulator) I get the following in the output window of visual studio:
When this is run for subsequent times the debug statements in the output window of visual studio depend on how long the interval between clicking the button.
The following shows 2 values because the time interval between clicks is greater than 20 seconds. The first value generated by the instance of the GeoCoordinateWatcher class is ignored.
To show this is not an attribute of the currently executing application session, I've added an explicit finalise to the main page as well as a debug statement in the constructor. This shows that once the location services are initialised on the phone and the current location is requested then the last value is always stored by the device and published as the first event when the GeoCoordinateWatcher is started.
The above screen shot shows 3 distinct activations of the application after tomb-stoning the device twice.
As you can see the first run shows the last geo-location generated by the device was 4 hours prior, then we tomb-stoned the app for 30 seconds, then re-activated the app with a back press. Again because the previous geo-location was generated greater than 20 seconds ago it is ignored. Finally the last activation after tomb stoning was not greater than 20 seconds so it was not ignored.
This code has been incorporated into the WP7Contrib as the LocationService. It has method for getting the geo-location using time & distance thresholds as well. It also has provides the GeoCoordinateWatcher Status event as an observable method.
The code is available in this change-set or will be part of the 1.4 release due at the end of the month.
What we also discovered is that you can't always trust the first position event generated by this class.
We have a requirement to get the current location when a user clicks a button, the user could do this at any time when using the application - they could click once, a dozen times or not at all. Every time they click the button we need to get an accurate single value. The accuracy of the value depend on both the actual value and the age of the value. Every value generated by the GeoCoordinateWatcher class is published by via the PositionChanged event and this is made up of 2 components - the location value and the timestamp when the location value was generated, this is exposed as the GeoPosition<T> class.
The PositionChanged event is defined as follows. I've included the StatusChanged event to show all the event signatures for the class:
public class GeoCoordinateWatcher : IDisposable, INotifyPropertyChanged, IGeoPositionWatcher<GeoCoordinate>
{ public event EventHandler<GeoPositionChangedEventArgs<GeoCoordinate>> PositionChanged; public event EventHandler<GeoPositionStatusChangedEventArgs> StatusChanged; ... }
The point of interest is the type used with the event handler. The GeoPositionChangedEventArgs
public class GeoPositionChangedEventArgs<T> : EventArgs { public GeoPositionChangedEventArgs(GeoPosition<T> position); public GeoPosition<T> Position { get; } }public class GeoPosition<T>
{
public GeoPosition(); public GeoPosition(DateTimeOffset timestamp, T position); public T Location { get; set; } public DateTimeOffset Timestamp { get; set; } }
We use the following code to return a single geo-location value when the button is clicked. It uses Rx (reactive extensions) to wrap up the call to the GeoCoordinateWatcher class. We don't just use the 'FromEventPattern' Rx method because we don't want the instance of the GeoCoordinateWatcher running riot and generating a gazillion location results and killing the UI thread!
The highlighted areas show how I am using the time offset to determine the validity of the location published by the GeoCoordinateWatcher class. In this demo instance I am saying - 'if the position timestamp is greater than 20 seconds ago then ignore the value...'. When valid the new location is published using the Rx method 'OnNext' on the returned Subject<T>. We also call the Rx 'OnCompleted' method to make the 'Finally' method execute and shut down the watcher by calling 'watcher.Stop()'.
Note: We also do the 'Start' of the GeoCoordinateWatcher on a background thread to prevent blocking of the current thread - in this case the UI thread.
This class is the used in the page class as follows:
When run for the first time (on the emulator) I get the following in the output window of visual studio:
When this is run for subsequent times the debug statements in the output window of visual studio depend on how long the interval between clicking the button.
The following shows 2 values because the time interval between clicks is greater than 20 seconds. The first value generated by the instance of the GeoCoordinateWatcher class is ignored.
To show this is not an attribute of the currently executing application session, I've added an explicit finalise to the main page as well as a debug statement in the constructor. This shows that once the location services are initialised on the phone and the current location is requested then the last value is always stored by the device and published as the first event when the GeoCoordinateWatcher is started.
The above screen shot shows 3 distinct activations of the application after tomb-stoning the device twice.
As you can see the first run shows the last geo-location generated by the device was 4 hours prior, then we tomb-stoned the app for 30 seconds, then re-activated the app with a back press. Again because the previous geo-location was generated greater than 20 seconds ago it is ignored. Finally the last activation after tomb stoning was not greater than 20 seconds so it was not ignored.
This code has been incorporated into the WP7Contrib as the LocationService. It has method for getting the geo-location using time & distance thresholds as well. It also has provides the GeoCoordinateWatcher Status event as an observable method.
The code is available in this change-set or will be part of the 1.4 release due at the end of the month.
Comments
Post a Comment