Part II - The WiE Mobile Client: Architecture & Design - Collecting GPS data and storing it into a SQL Compact database.

 

In last week’s article I introduced the WiE Community project I have started working on. WiE is a location-based social networking community built to demonstrate an end to end application built on top of SQL Server Compact, SQL Server Data Services and SQL Server 2008.

In this week’s article I describe the overall architecture and go into the details of the design for WiE Mobile Client component.

WiE Architecture

The WiE architecture is made up of several components, these include: a Windows Mobile client using a SQL Server Compact database for local storage, a SQL Server Data Services storage layer and a SQL Server 2008 based spatial rules engine.

WiE Architecture

WiE Mobile Client

The Mobile client currently has a small set of requirements to satisfy:

· The Mobile Client should allow a person to join the WiE Community from their handset and become a member of the community.

· The Mobile Client should periodically collect the device’s location and transmit that location history to the centralized data store so that it can be picked up by the spatial rules engine.

· The Mobile Client should, if available collect the device’s speed, heading and altitude.

While that seems simple enough, in the world of mobile development and wireless we need to handle some special cases:

· Lack of signal, no connectivity or dropped connectivity to the Internet. This could include user specified lack of connectivity, for example “Airplane Mode” on a phone.

· Restrictions on amount of data transfers allowed by wireless carriers. Most companies now offer “unlimited” data plans, but they do monitor for excesses.

In short the WiE Mobile Client design needs to support the scenario we refer to as the occasionally disconnected application.

WiE Mobile Client Design

Like most folks I tend to leverage my past experience and past designs when I start a new project and this was no exception. I adopted a similar high level design for the mobile client as the one I had used a few years ago for a J2ME based mobile job management application (see previous blog).

The Mobile Client uses a Model-View-Controller pattern where the Model is responsible for hiding the complexity of dealing with databases or remote data stores away from the application layer.

GPS Data Flow

I will only cover the View and Controller and GPS module cursorily in this article as most of the implementation details are contained in the Model.

 

The Views and Controller

The initial implementation of the WiE Mobile Client consists of a primary View (MainForm) responsible for displaying the current location of the device and updating itself on changes. This form also takes on the duties of the controller (we will split this out when we expand the application).

public partial class MainForm : Form

{

   . . .

   // Handler for display updates in the display thread.

   private EventHandler m_updateDisplayHandler = null;

   // Gps Module and Gps related state

   private Gps m_gps = new Gps();

   

   . . .

  // The data model for the application

   private IWiEModel m_model = null;

   /// <summary>

   /// Constructor for the main form

   /// </summary>

   public MainForm()

   {

      InitializeComponent();

      // Try to load previous settings if any

      LoadSettings();

  // Try to create the (data) model for the application

   m_model = new WiEModelWithLocalCache();

           

      // Initialize the model as we will be using it to retrieve

      m_model.Initialize();

   . . .

 

The GPS Module

I hate to reinvent the wheel, but I have been guilty of it in the past. Whenever possible I try to find existing libraries, controls or source code before implementing a new application. In this case I was looking for sample code for accessing the GPS capabilities of a Windows Mobile device, preferably as a managed code library. As it turns out there was an example library that took care of wrapping the native calls to Windows CE into a nice set of managed c# classes. You can find that library as part of the Windows Mobile 6 SDK.

If you have installed the Windows Mobile 6 SDK in its default location, you will find the example at: C:\Program Files\Windows Mobile 6 SDK\Samples\PocketPC\CS\GPS

There is also a short MSDN article that covers the use of the sample at: https://msdn.microsoft.com/en-us/library/bb158708.aspx.

I took the SDK sample and repackaged it into a separate project and generated a GPSModule assembly (dll) that is referred to by the main mobile client project.

 

Using the GPS Module

On startup the application’s main view creates an instance of the GPS class and in the form load event handler the application subscribes to the LocationChanged events and DeviceStateChanged events raised by the GPS Module.

/// <summary>

/// Event callback when form is first loaded. We initialize the GPS event handlers here and

/// the event handler for redrawing / updating the UI.

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private void OnFormLoad(object sender, EventArgs e)

{

  m_updateDisplayHandler += new EventHandler(UpdateDisplayRequest);

  m_gps.DeviceStateChanged += new DeviceStateChangedEventHandler(OnDeviceStateChanged);

  m_gps.LocationChanged += new LocationChangedEventHandler(OnLocationChanged);

}

 

The OnLocationChanged() event handler is called whenever the GPS module reports a new position or a change in device state. The method is responsible for verifying that the new position is valid, for converting it into a LocationDataObject and storing it with the model.

/// <summary>

/// Event callback when a new location is received from the GPS module.

/// </summary>

/// <param name="sender"></param>

/// <param name="args"></param>

void OnLocationChanged(object sender, LocationChangedEventArgs args)

{

  // Retrieve the GPS position information from the args

  m_currentPosition = args.Position;

  try

  {

  // Only do this if we got GPS data.

    if ((m_gps.Opened) && (m_currentPosition != null))

    {

    // We need at least the longitude and the latitude for this to be worth saving...

      if (m_currentPosition.LatitudeValid && m_currentPosition.LongitudeValid && m_currentPosition.TimeValid && m_currentPosition.SatellitesInViewCountValid)

      {

      // Ok, now populate the new Location Data Object

        WiELocationDataObject newLocation = new WiELocationDataObject();

        // Set up the base required fields (Long, Lat, Time, SatellitesInView)

        newLocation.LocationID = System.Guid.NewGuid();

        newLocation.MemberID = m_guidMemberID;

        newLocation.DeviceID = m_guidDeviceID;

        newLocation.Longitude = m_currentPosition.Longitude;

        newLocation.Latitude = m_currentPosition.Latitude;

. . .

        // Now avoid saving too much data by applying a data capture governot that only saves

    // every [m_nMinimumDistanceBetweenLocationInMeters] meters or every

        // [m_nMaximumEllapsedTimeBetweenLocationInSeconds] seconds, whichever comes first.

        double dDistanceFromPreviousPoint = (null == m_previousLocation) ? m_nMinimumDistanceBetweenLocationInMeters : newLocation.DistanceTo(m_previousLocation);

        double dSecondsEllapsedSincePreviousPoint = (null == m_previousLocation) ? m_nMaximumEllapsedTimeBetweenLocationInSeconds : ((TimeSpan)newLocation.DateCollected.Value.Subtract(m_previousLocation.DateCollected.Value)).TotalSeconds;

        if ((dDistanceFromPreviousPoint >= m_nMinimumDistanceBetweenLocationInMeters) ||

            (dSecondsEllapsedSincePreviousPoint >= nMaximumEllapsedTimeBetweenLocationInSeconds))

        {

   // Attempt to save the the location

            m_model.SaveLocation(newLocation);

           // Remember the location for next time around in case we want to implement a simple

           // governor to minimize amount of data collected.

       m_previousLocation = newLocation;

        }

     }

. . .

   // Update the display to show the current location

  Invoke(m_updateDisplayHandler);

}

The OnLocationChanged() method implements a data governor feature that limits the amount of location data collected if the device is staying in the same location for an extended period of time. The governor supports two thresholds, one based on distance from previous reported location and the other based on time elapsed since last reported location. These thresholds are configurable.

The distance threshold is implemented using the law of cosines which enables a rough approximation of distance between to GPS Longitude / Latitudes. The implementation of the distance method is shown below:

class WiELocationDataObject: WiEDataObject

{

. . .

  /// <summary>

  /// Calculates the distance in meters between this LocationObject and the location

  /// object specifed in p_destinationLocationObject

  ///

  /// Uses the law of cosines to calculate the distance.

  /// </summary>

  /// <param name="p_destinationLocationObject"></param>

  /// <returns></returns>

  public Double DistanceTo(WiELocationDataObject p_destinationLocationObject)

  {

    const double dEarthRadiusInMeters = 6371000;

  double dDistance = 0;

  double dLatitudeSource = ToRadians((double)Latitude);

  double dLongitudeSource = ToRadians((double)Longitude);

  double dLatitudeDestination = ToRadians((double)p_destinationLocationObject.Latitude);

  double dLongitudeDestination = ToRadians((double)p_destinationLocationObject.Longitude);

  double a = Math.Sin(dLatitudeSource) * Math.Sin(dLatitudeDestination);

  double b = Math.Cos(dLatitudeSource) * Math.Cos(dLatitudeDestination) * Math.Cos(dLongitudeDestination - dLongitudeSource);

    double c = Math.Acos(a + b);

 

    dDistance = Math.Abs(dEarthRadiusInMeters * c);

  return dDistance;

  }

}

 

 

The Model

The model is responsible for offering a data access layer to the application and abstracts away the complexities and differences between local and remote storage.

The contract that a Model must adhere to is specified by the IWiEModel interface. This interface defines a set of methods and events to store and retrieve the three basic data objects that make up the domain of the application (Member, Device and Location data objects).

interface IWiEModel

{

   event EventHandler<LocationHistoryChangedArgs> RaiseLocationHistoryChangedEvent;

   event EventHandler<DeviceChangedArgs> RaiseDeviceChangedEvent;

   event EventHandler<MemberChangedArgs> RaiseMemberChangedEvent;

   WiELocationDataObject GetLocation(System.Guid p_guidLocationID);

   void RemoveLocation(System.Guid p_guidLocationID);

   List<WiELocationDataObject> GetLocationHistoryBetween(DateTime p_dtStartTimeUTC, DateTime p_dtStopTimeUTC);

   List<WiELocationDataObject> GetLocationHistoryBefore(DateTime p_dtEndTimeUTC);

   void SaveLocation(WiELocationDataObject p_locationDataObject);

   void SaveMember(WiEMemberDataObject p_memberDataObject);

   WiEMemberDataObject GetMember(Guid p_guidMemberID);

   WiEMemberDataObject GetMemberByUserName(string p_strUserName);

   WiEMemberDataObject GetMemberByPhoneNumber(string p_strPhoneNumber);

   void SaveDevice(WiEDeviceDataObject p_deviceDataObject);

   WiEDeviceDataObject GetDevice(System.Guid p_guidDeviceID);

   List<WiEDeviceDataObject> GetDevicesByMember(System.Guid p_guidMemberID);

   void Initialize();

   void Terminate();

}

A Plug & Play Data Access Layer Approach

There are actually three concrete implementations of the IWiEModel interface used by the client application. These work together to implement an application model that supports both local and remote storage operations with support for the occasionally disconnected case mentioned earlier.

· WiELocalModel: implements the model interface for a local storage based data model using ADO.Net against a SQL Server Compact Database.

· WiERemoteModelSSDSForMobile: implements the model interface for storage and retrieval against our Cloud storage offering, SQL Server Data Services (formerly codename Sitka) using SOAP web service calls.

· WiEModelWithLocalCache: implements the occasionally disconnected data model as a wrapper or façade to the WiELocalModel and the WiERemoteModel implementations along with a SyncAgent object to keep the two in synch.

This design allows applications to plug in an appropriate provider for the task at hand. We could use any of these three implementations (or new implementations in the future) as the application’s data model. If we could guarantee great and constant connectivity we might choose to use the WiERemoteModelSSDSForMobile implementation as the application’s data model and not worry about local storage. However, in the case of this application however we will use the WiEModelWithLocalCache implementation as we want to support the occasionally disconnected scenario.

We will discuss the implementations of WiELocalModel, the WiERemoteModel and the Sync agent in detail in upcoming blog articles.

Enjoy and please let me know what additional topics you would like me to cover as part of this series.

Sincerely,

Olivier