Mike Taulty's Blog
Bits and Bytes from Microsoft UK
Windows/Phone 8.1 and the “Rolling Geofence”

Blogs

Mike Taulty's Blog

Elsewhere

Archives

I have a vague, nagging idea for an app to run on the Phone but the app needs to know the user’s location.

It’s not the type of app that needs to know the location on a blow-by-blow basis. It’s not like a jogging-tracker type app or that kind of thing but the app will need to know the user’s location as the user is mobile rather than waiting until they actually use the app and then trying to figure out where they are.

In simple terms – I might want to have a live tile for the app with the user’s current location displayed on it. It doesn’t need to be 100% accurate 100% of the time but it needs to be “mostly right”.

Windows Phone 8.0 has support for the type of application that constantly monitors the device’s location;

Running location-tracking apps in the background for Windows Phone 8

and the support is specifically about location tracking apps in that an app using this mechanism can only do certain things in the background.

Now, Windows/Phone 8.1 apps written with WinRT (not Silverlight) can’t replicate that functionality today. If you need that constant location tracking then you’re only going to get that on Windows Phone 8.1 and you’re only going to get it by writing a Silverlight app (either an 8.0 app or an 8.1 app).

But, Windows/Phone 8.1 introduce the much more general approach to background working of having background tasks which are fired by triggers with a task running in the application’s context (i.e. able to get to the app’s files, settings, etc.) and triggers supporting many scenarios (e.g. internet connection available) including location based triggers which, today, are tied to geo-fences.

If you want to see more on background tasks on Windows Phone 8.1, you can watch this session from //Build;

and if you’re interested in geo-fencing on the Phone you can watch this one;

I’ve experimented with the geo-fencing APIs before on Windows 8.1 without massive success with my main challenge being one of trying to simulate (via the Windows 8.1 simulator) location because working with location is really difficult if you don’t have a great way of simulating where the device is.

With the arrival of Windows Phone 8.1 I wanted to think about this idea of my app that “tracks location” another time and see if I could get something started on it.

Realising that I can’t take the “continuous tracking” approach of Windows Phone 8.0 and, also, realising that I really don’t need it I wondered whether I could make use of geo-fencing to achieve what I wanted and this led me to the idea of…

Rolling GeoFences

Ok, I made that term up. I daresay someone else made it up before me but I haven’t web searched for it to check. The basic idea of the “rolling geofence” is to;

  1. Get the user’s current location.
  2. Register a geofence around that location of some radius (let’s say 500m) asking the system to notify me when the device leaves that geofenced area.
  3. Register a background task which will be invoked when the geofence is broken.
  4. Have the background task clear the previous geofence and go back to step 1.

and with Windows/Phone 8.1 supporting circular geofences that means that if a user is at some Lat,Lon and my app creates a fence of some radius R then I’d hope that when they move a distance of R I get a chance to put a new circle around them centred at their new location with ( if the timing was right ) the circumference of that circle touching their original position.

That’s the theory, anyway Smile

Now, I know that on the phone I’m possibly not going to get my geofence events when they happen – in Windows Phone if my background task was to cause multiple geofence events one after the other then they would be coalesced and delivered only every 2 minutes (watch the second video above at the 52 minute mark for details).

For my app, that perhaps doesn’t matter too much – if I was to use a 1km radius to try and track where the device is then I guess I might start to miss individual events at the point where the device was travelling more than 500m in 2 minutes or 15km/hour but as long as those events are delivered at least every 2 minutes I’m not really going to get too much out of sync with where the user is and I could always use a wider geofence to try and minimise the impact of this.

Experimenting

I made a little WinRT component project and added a few classes to it;

image

and 3 of these are exposed outside of the WinRT component library – the RollingGeofence class (which is badly named), the PositionLogEntry and the RollingGeofenceBackgroundTask class. The last one is exposed because I think you have to make implementation of IBackgroundTask public although in this case I’d rather not expose it otherwise.

Those classes look like this;

namespace RollingGeofence
{
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Threading.Tasks;
  using Windows.ApplicationModel.Background;
  using Windows.Devices.Geolocation;
  using Windows.Devices.Geolocation.Geofencing;
  using Windows.Foundation;

  public static class RollingGeofence
  {
    static PositionLogger logger;

    static RollingGeofence()
    {
      logger = new PositionLogger(LOG_FILE);
    }
    public static bool IsRegistered
    {
      get
      {
        return (IsTaskRegistered || IsFenceRegistered);
      }
    }
    public static IAsyncAction RegisterAsync(double fenceRadius)
    {
      return (InternalRegisterAsync(fenceRadius).AsAsyncAction());
    }
    public static void Unregister()
    {
      UnregisterFence();
      UnregisterTask();
    }
    public static IAsyncOperation<IEnumerable<PositionLogEntry>> GetLogFileEntriesAsync()
    {
      return (InternalGetLogFileEntriesAsync().AsAsyncOperation());
    }
    public static void AddTaskCompletedHandler(
      BackgroundTaskCompletedEventHandler handler)
    {
      TaskRegistration.Completed += handler;
    }
    public static void RemoveTaskCompletedHandler(
      BackgroundTaskCompletedEventHandler handler)
    {
      TaskRegistration.Completed -= handler;
    }
    public static IAsyncAction ClearLogAsync()
    {
      return (logger.TruncateLogAsync().AsAsyncAction());
    }
    static async Task<IEnumerable<PositionLogEntry>> InternalGetLogFileEntriesAsync()
    {
      var entries = await logger.ReadEntriesAsync();
      return (entries);
    }
    internal static async Task CheckReportsAndRegisterAsync(
      IEnumerable<GeofenceStateChangeReport> reports)
    {
      if (reports != null)
      {
        GeofenceStateChangeReport report = reports.FirstOrDefault(
          r =>
            (r.NewState == GeofenceState.Exited) &&
            (r.Geofence.Id == FENCE_ID));

        if (report != null)
        {
          double radius =
            ((Geocircle)report.Geofence.Geoshape).Radius;

          await RegisterFenceAsync(radius);
        }
      }
    }
    static async Task InternalRegisterAsync(double fenceRadius)
    {
      try
      {
        if (IsRegistered)
        {
          throw new InvalidOperationException("Already registered");
        }
        await RegisterFenceAsync(fenceRadius);
        await RegisterTaskAsync();
      }
      catch
      {
        Unregister();
        throw;
      }
    }

    static void UnregisterFence()
    {
      if (IsFenceRegistered)
      {
        Geofence fence = GeofenceMonitor.Current.Geofences.First(
          f => f.Id == FENCE_ID);

        GeofenceMonitor.Current.Geofences.Remove(fence);
      }
    }
    static void UnregisterTask()
    {
      if (IsTaskRegistered)
      {
        TaskRegistration.Unregister(false);
      }
    }
    static IBackgroundTaskRegistration TaskRegistration
    {
      get
      {
        if (!IsTaskRegistered)
        {
          throw new InvalidOperationException("background task not yet registered");
        }
        IBackgroundTaskRegistration registration = 
          BackgroundTaskRegistration.AllTasks.First(t => t.Value.Name == TASK_NAME).Value;
        
        return (registration);
      }
    }
    static async Task RegisterTaskAsync()
    {
      await BackgroundExecutionManager.RequestAccessAsync();

      BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
      builder.Name = TASK_NAME;
      builder.SetTrigger(new LocationTrigger(LocationTriggerType.Geofence));
      builder.TaskEntryPoint = typeof(RollingGeofenceBackgroundTask).FullName;
      builder.Register();
    }
    static async Task<BasicGeoposition> GetCurrentLocationAsync()
    {
      Geolocator locator = new Geolocator();

      Geoposition position = await locator.GetGeopositionAsync(
        TimeSpan.FromSeconds(LOCATION_AGE_SEC),
        TimeSpan.FromSeconds(LOCATION_TIMEOUT_SEC));

      return (position.Coordinate.Point.Position);
    }
    static async Task LogPositionAsync(BasicGeoposition position)
    {
      await logger.Log(position);
    }
    static async Task RegisterFenceAsync(double fenceRadius)
    {
      // Where are we?
      BasicGeoposition position = await GetCurrentLocationAsync();

      // Log where we are?
      await LogPositionAsync(position);

      Geofence fence = null;

      // Get rid of any existing geofences. This is a bit of a TBD. The fence I'm
      // creating is meant to be a single-use fence so my understanding would be
      // that the system gets rid of it for me. Maybe the system will do that 
      // "later" but, right now, it's still there so if I try and add a fence
      // with the same ID I'll get an error so I remove the original fence
      // myself.
      if (IsFenceRegistered)
      {
        fence = GeofenceMonitor.Current.Geofences.First(
          f => f.Id == FENCE_ID);

        GeofenceMonitor.Current.Geofences.Remove(fence);
      }
      fence = new Geofence(
        FENCE_ID,
        new Geocircle(
          position,
          fenceRadius),
        MonitoredGeofenceStates.Exited,
        true,
        TimeSpan.FromSeconds(DWELL_TIME_SEC));

      GeofenceMonitor.Current.Geofences.Add(fence);
    }
    static bool IsTaskRegistered
    {
      get
      {
        var hasTask = BackgroundTaskRegistration.AllTasks.Any(reg =>
          reg.Value.Name == TASK_NAME);

        return (hasTask);
      }
    }
    static bool IsFenceRegistered
    {
      get
      {
        var hasFence = GeofenceMonitor.Current.Geofences.Any(
          fence => fence.Id == FENCE_ID);

        return (hasFence);
      }
    }
    static readonly int LOCATION_TIMEOUT_SEC = 10;
    static readonly int LOCATION_AGE_SEC = 60;
    static readonly int DWELL_TIME_SEC = 15;
    static readonly string FENCE_ID = "_rollingFence";
    static readonly string TASK_NAME = FENCE_ID + "Task";
    static readonly string LOG_FILE = "positionLog.txt";
  }
}

and so this RollingGeofence class is a big static class which has most of the functionality that I’ve built here. The public interface is much simpler essentially comprising;

  1. A RegisterAsync() and Unregister method. Registration involves registering the background task after setting up the first geofence at the user’s current location.
  2. An Add/RemoveTaskCompletedHandler methods to add a method to be invoked whenever the background task that this library creates completes. This is so a potential foreground app can get a notification whenever the background task runs and publishes new location data.
  3. A GetLogFileEntriesAsync() method which reads the log file that the background task ultimately populates.
  4. A ClearLogAsync() method which gets rid of the log file that the background task ultimately populates.

and that’s it. That class is supported by the background task implementation itself;

namespace RollingGeofence
{
  using System.Collections.Generic;
  using Windows.ApplicationModel.Background;
  using Windows.Devices.Geolocation.Geofencing;

  // Shame that this has to be public but it's understandable.
  public sealed class RollingGeofenceBackgroundTask : IBackgroundTask
  {
    public async void Run(IBackgroundTaskInstance taskInstance)
    {
      var deferral = taskInstance.GetDeferral();

      try
      {
        IReadOnlyList<GeofenceStateChangeReport> reports = 
          GeofenceMonitor.Current.ReadReports();

        // check reports for our geofence and if it's exited replace it with a new one.
        // slight problem here if this fails because it'll mean that we won't get
        // invoked again in the future and so we'll fail and have little chance of 
        // repair :(
        await RollingGeofence.CheckReportsAndRegisterAsync(reports);
      }
      finally
      {
        deferral.Complete();
      }
    }
  }
}

and this task is simply handing over the responsibility for checking the GeofenceStateChangeReport entries received to the RollingGeofence class.

Then there’s 2 other small classes;

namespace RollingGeofence
{
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Threading.Tasks;
  using Windows.Devices.Geolocation;
  using Windows.Storage;

  internal class PositionLogger
  {
    internal PositionLogger(string fileName)
    {
      this.fileName = fileName;
    }
    internal async Task Log(BasicGeoposition position)
    {
      StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(
        this.fileName, CreationCollisionOption.OpenIfExists);

      PositionLogEntry entry = PositionLogEntry.FromBasicGeoposition(position);

      await FileIO.AppendLinesAsync(file, new string[] { entry.ToLogEntry() });
    }
    internal async Task<IEnumerable<PositionLogEntry>> ReadEntriesAsync()
    {
      List<PositionLogEntry> list = new List<PositionLogEntry>();

      try
      {
        StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(
          this.fileName);

        IList<string> lines = await FileIO.ReadLinesAsync(file);

        list.AddRange(
          lines.Select(
            line => PositionLogEntry.FromLogEntry(line)
          )
        );
      }
      catch (FileNotFoundException)
      {

      }
      return (list);
    }
    internal async Task TruncateLogAsync()
    {
      try
      {
        StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(
          fileName);

        await file.DeleteAsync();
      }
      catch (FileNotFoundException)
      {

      }
    }
    string fileName;
  }
}

and;

namespace RollingGeofence
{
  using System;
  using Windows.Devices.Geolocation;

  public sealed class PositionLogEntry
  {
    private PositionLogEntry()
    {    
    }
    internal static PositionLogEntry FromBasicGeoposition(BasicGeoposition position)
    {
      PositionLogEntry entry = new PositionLogEntry()
      {
        Date = DateTimeOffset.Now,
        Latitude = position.Latitude,
        Longitude = position.Longitude,
        Altitude = position.Altitude
      };
      return (entry);
    }
    internal static PositionLogEntry FromLogEntry(string logFileEntry)
    {
      string[] pieces = logFileEntry.Split('|');
      PositionLogEntry entry = new PositionLogEntry()
      {
        Date = DateTimeOffset.Parse(pieces[0]),
        Latitude = double.Parse(pieces[1]),
        Longitude = double.Parse(pieces[2]),
        Altitude = double.Parse(pieces[3])
      };
      return (entry);
    }
    public string ToLogEntry()
    {
      return (string.Format(
       "{0}|{1}|{2}|{3}",
       this.Date.ToUniversalTime(),
       this.Latitude, this.Longitude, this.Altitude));
    }
    public override string ToString()
    {
      return (string.Format("[{0:g}]: Lat [{1}], Lon [{2}], Alt [{3}]]",
        this.Date,
        this.Latitude,
        this.Longitude,
        this.Altitude));
    }
    public DateTimeOffset Date { get; internal set; }
    public double Latitude { get; internal set; }
    public double Longitude { get; internal set; }
    public double Altitude { get; internal set; }
  }
}

Unpicking that Code

Just to step back into that code a little – the essence of the previous code is in the RollingGeofence and RollingGeofenceBackgroundTask classes and is something like;

  1. The intention is that some caller will perform a one-time RollingGeofence.RegisterAsync(). That method will then;
    1. Attempt to get the current location.
    2. Register a geofence of a certain radius around that location. The fence requested is a “single use” fence and specifies that I want to be notified when the device exits that fence for more than 15 seconds.
    3. Register a background task to run whenever a geofence notification for one of my geofences is received.
  2. The background task;
    1. Simply calls back into the RollingGeofence class asking it to take a look at the fence notifications and if any state that our one-and-only fence has been exited then we get rid of the fence and then repeat the three steps 1.1 to 1.3 above.

Any time we obtain the current location we log it into a file and that file can be read (in a slightly abstracted way) via the public API of RollingGeofence.

Setting up a Host Project

I need some “app” project to try this code out and so I made a Windows Phone 8.1 project, referenced by RollingGeofence component and wrote a tiny piece of UI;

image

and that marries up with a small amount of code-behind to try things out;

namespace App1
{
  using RollingGeofence;
  using System;
  using System.Collections.ObjectModel;
  using System.Linq;
  using Windows.ApplicationModel.Background;
  using Windows.UI.Core;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;

  public sealed partial class MainPage : Page
  {
    public MainPage()
    {
      this.InitializeComponent();
    }
    async void OnRegister(object sender, RoutedEventArgs e)
    {
      await RollingGeofence.RollingGeofence.RegisterAsync(500);
    }
    void OnUnregister(object sender, RoutedEventArgs e)
    {
      RollingGeofence.RollingGeofence.Unregister();
    }
    async void OnReadLogFile(object sender, RoutedEventArgs e)
    {
      var entries = await RollingGeofence.RollingGeofence.GetLogFileEntriesAsync();
      
      this.entries = new ObservableCollection<PositionLogEntry>(
        entries);

      this.DataContext = this.entries;

      RollingGeofence.RollingGeofence.AddTaskCompletedHandler(OnTaskCompleted);
    }
    void OnTaskCompleted(BackgroundTaskRegistration sender,       
      BackgroundTaskCompletedEventArgs args)
    {
      Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
        async () =>
        {
          var newEntries = await RollingGeofence.RollingGeofence.GetLogFileEntriesAsync();

          if (this.entries.Count > 0)
          {
            newEntries = newEntries.Where(
              e => e.Date > this.entries[this.entries.Count - 1].Date);
          }
          foreach (var item in newEntries)
          {
            this.entries.Add(item);
          }
        });
    }
    async void OnClearLogFile(object sender, RoutedEventArgs e)
    {
      this.entries.Clear();
      await RollingGeofence.RollingGeofence.ClearLogAsync();
    }
    ObservableCollection<PositionLogEntry> entries;
  }
}

the other thing that needs setting up is a manifest. At this point I get into a philosophical debate. The component that I’ve written is trying (in as much as possible) to abstract the details of background tasks from any calling app. However, in order to register a background task and to obtain the user’s location the app itself is going to have to have a manifest that allows for those 2 things. I almost feel like there’s a need for a “component manifest” where I could specify these things without troubling any developer that wanted to use my component but, there isn’t, and so I alter the manifest file;

image

image

Trying Things Out

This is where things get a little “hairy”. I’d prefer to try this code out initially on the emulator rather than having to put it onto my phone and drive around a lot with the phone to see what happens Smile

However, I’m not entirely sure that I understand how to best use the emulator to test out geofencing and I find that my experiments are a bit hit and miss. What I can say is that I seem to have found a way to make things work reliably but that’s been more by luck than by judgement to date. I specifically think that the geofencing service has quite a lot of smarts and the sort of tricks I might use in the emulator to test it are actually being filtered out by its algorithms when (e.g.) it sees the device suddenly travel 100km in a short period of time.

Here’s the process I follow;

  1. Debug the app on the emulator.
  2. Set up a route of points that represent some journey over a few miles or so. My route looks something like this;

image

  • The route is using pin-point accuracy, travelling at the speed limit.
  • Set the route playing in the emulator. This seems to kick-start the process of the device thinking that it has a location.
  • Tap on my “Register” button in order to set up the geofence and the background task registration.
  • Tap on my “Initial Read Log File” button in order to read the initial log file (empty) and set up a notification handler for whenever the background task completes to re-read that log file.
  • Wait to see if the background task runs and the location updates over time.

and that seems to work reasonably well and here’s a picture of the app as it has recorded 3 changes in location as the device travels down the road;

image

however, I found that my notifications stopped for quite a while after the 3rd one leaving a 16 minute gap where I didn’t received any geofence notifications.

At first, I thought I’d hit a bug where my code had failed to register the “next” geofence but that can’t have been the case because I did get a subsequent notification around 16 minutes later and then things were a little sporadic again for the next few miles of the route.

It seems to hold up a little running in the emulator so I’ll install it to my phone and see to what extent it tracks me there (and whether I notice my battery life dropping) over the next couple of days.

The main thing that worries me with the approach that I’ve used here of;

  • get location
  • create fence
  • wait for exit fence
  • remove fence
  • repeat

is that if something goes wrong in the sequence then the next geofence might not get set up and the whole “chain” will stop. I could possibly try and mitigate that a little by (e.g.) having another background task like a time or maintenance trigger which periodically tried to make sure that my fence was in place. I might look into that as an additional thing to try.

Update 1 – 7th May, 2014

Having run this code on my phone for a week or so I can certainly say that it doesn’t quite work properly. What I’ve found so far is that I do get notifications as I change location but there’s a couple of problems;

  1. There seems to come a point at which the notifications stop. I suspect that this is because I have something that’s a little “brittle” in the way in which I handle one geofence being exited and a new geofence needing to be set up. Specifically, I suspect that I am making a call to determine the current location and that call might be failing which then leaves my background task in a situation where it has not registered any geofences at all. I’ve instrumented my code a little to see if I can prove that this is what’s happening in my code.
  2. Notifications can seem to be somewhat sporadic. For instance – I might move 5+ miles from my current location but not see a geofence notification until quite some time later.

I’ve revised my code a little to see if I can improve the results and am testing again on my own phone as it follows me around my travels…


Posted Fri, Apr 25 2014 3:21 PM by mtaulty

Comments

John wrote re: Windows/Phone 8.1 and the “Rolling Geofence”
on Sun, Apr 27 2014 8:48 PM

Hi there! I just found your site and I was thinking EXACTLY the same thing! How is it working out? You could solve your problem by including say 10 somewhat overlapping circular fences around the current location to make sure when we enter those we get notified.

However, are you planning on releasing this? Or could you send me the test project? This is the ONE thing I am missing from my windows phone (continuous GPS logging) now that I have swype (missed that too ;)).

Cool thing you are trying this out. I just ordered my Samsung Galaxy (so missing my lattitude ;)), but I am thinking about cancelling since I see a WP developer picked up the same idea!

Good luck!

John wrote re: Windows/Phone 8.1 and the “Rolling Geofence”
on Sun, Apr 27 2014 8:52 PM

The link contains my reddit question about it ;)

mtaulty wrote re: Windows/Phone 8.1 and the “Rolling Geofence”
on Fri, May 2 2014 11:11 AM

John

Not sure I'll release anything here- I'm still trying it out on my phone right now.

Mike

GeraldMaffeo wrote re: Windows/Phone 8.1 and the “Rolling Geofence”
on Tue, Jun 17 2014 4:19 PM

I had also been thinking along these lines, but I seem o recall it requires network connectivity. If true, that's a deal-breaker in most of the areas I hike in (mountains).