This is part 2 of a series of blog posts on the design and implementation of the location-aware Windows Store app "Location Data Logger". Download the source code to Location Data Logger here.
The DataLogger Class
At the heart of Location Data Logger is the DataLogger object which is responsible for obtaining location reports from the Windows 8 Geolocation sensor and sending that information to the various other components in the application. All of this is implemented within the DataLogger class.
Location Data Logger is a relatively simple application and it only has one display page, and I could easily have implemented the geolocation funcitonality into the MainPage class. I chose to go with a separate class for two reasons:
- For anything more than trivial applications, it is good application design to compartmentalize your objects. Rather than have MainPage be some mega-class that implements everything from geolocation to writing the data files, I broke the application out into functional components.
- Future-proofing. If I decide to add a second display page or state to the application, the code is already capable of supporting that.
Since this object coordinates all of the activities within the application, it needs to be able to communicate with the relevant Windows 8 sensors as well as the objects that are responsible for writing the data logs. Some of its private class members include:
Geolocator geo; SimpleOrientationSensor sorient; ExportCSV eCSV; ExportGPX eGPX; ExportKML eKML;
Initialization
When the DataLogger object is created, considerable initialization takes place.
public DataLogger()
{
lastupdate = new DateTime(1900, 1, 1);
hp_source = hp_tracking = running= false;
position_delegate = null;
status_delegate = null;
logCSV = logGPX = false;
logKML = true;
geo = null
sorient = SimpleOrientationSensor.GetDefault();
folder = null;
eGPX= new ExportGPX();
eCSV = new ExportCSV();
eKML = new ExportKML();
}
The Geolocator object is initialized inside of the Resume() method, which is called from MainPage when the application is ready to start or resume tracking the device’s position (though not necessarily logging).
public void Resume() { geo = new Geolocator(); geo.DesiredAccuracy = PositionAccuracy.High; geo.MovementThreshold = 0; geo.StatusChanged += new TypedEventHandler<Geolocator, StatusChangedEventArgs>(geo_StatusChanged); geo.PositionChanged += new TypedEventHandler<Geolocator, PositionChangedEventArgs>(geo_PositionChanged); }
Though all of this work takes place in two separate places, I’ll discuss them as a whole.
The geolocator sensor is initialized, and immediately configured with a MovementThreshold of 0 and a DesiredAccuracy of High. Most, if not all, GPS receivers calculate their position once per second, and the goal of the application is to record every position report received even when the position has not changed. These settings ensure we receive reports from the location device as the are reported, and prevent the Windows Sensor API from filtering some out.
Event handlers for the Geolocator‘s PositionChanged and StatusChanged events are also installed, a topic that I cover in detail below.
While I initialize a SimpleOrientation sensor, I do not create an event handler for it. This is because the data logger records the device’s orientation at the time a position update comes in, not when the orientation changes. This means an event handler is not only unnecessary, but unwanted.
Why include the SimpleOrientation sensor at all, though? It’s certainly not necessary for geolocation. The answer is because this information might be useful to a device manufacturer. A device’s antenna design can have a significant affect on the reception quality of radio signals, and reception can be orientation-sensitive.
Also note that I set two variables, hp_source and hp_tracking, to false, and initialize lastupdate to a time in the distant past (Jan 1st, 1900). These variables are used to internally determine and track 1) whether or not we have a high precision data source, and 2) if the user has asked to log only high precision data. Essentially what is happening here is that I assume the location data is not high-precision until proved otherwise.
The call from MainPage.xaml.cs that gets everything started looks like this:
public MainPage() { this.InitializeComponent(); … logger = new DataLogger(); logger.SetCallbackStatusChanged(update_status); logger.SetCallbackPositionChanged(update_position); logger.Resume(); … }
(The SetCallback* functions are explained below.)
Identifying high-precision geolocation data
The Location API in the Windows 8 Runtime abstracts the location source from the developer (and, in turn, the user). As explained in my blog “The WinRT Location API: Where did my location data come from?”, the geolocation sensor is actually a merging of multiple inputs, some specialized hardware devices such as GPS/GNSS receivers (if present), and some software sources such as WiFi triangulation. The API does not provide the developer with a means of explicitly determining where a location report originated. The best you can do is make an educated guess based on the reported accuracy and other characteristics of the position reports.
The DataLogger class looks at a combination of two factors: the update rate, and the Accuracy reported in the Geocoordinate object. This is done inside the log_position() method, which is called from the geo_PositionChanged() event handler:
TimeSpan deltat; deltat = c.Timestamp - lastupdate; // Do we have high-precision location data? if (deltat.TotalSeconds <= 3 && c.Accuracy <= 30) hp_source = true; else hp_source = false;
I somewhat arbitrarily choose a reporting interval of 3 seconds as the threshold, as some consumer GPS devices may update once a second but send position reports via their NMEA output stream every two seconds (this is to accommodate people using external GPS devices as a sensor via GPSDirect). The accuracy of 30 meters was also somewhat arbitrary: consumer GPS accuracy is typically on the order of a few meters, and car navigation systems can provide reasonable guidance with only 30 meters of accuracy.
Geolocation Events and Delegates
The DataLogger class implements event handlers for the PositionChanged and StatusChanged events so that the logger object can record the positions as they come in, as well as keep track of the status of the Geolocator sensor. One problem, though, is that the UI display needs to be updated as well, and so those events also need to reach the MainPage object. There are two options for accomplishing this:
- Have the MainPage object also register event handlers with the Geolocator object for the PositionChanged and StatusChanged events.
- Use delegates in the DataLogger object to call the appropriate methods in the MainPage class when PositionChanged and StatusChanged events arrive.
Both methods have their advantages and disadvantages. I went with second object because it limited the amount of redundant code, and also allowed me to pass additional information in the delegate that is not part of the PositionChanged event.
The callbacks are defined in the DataLogger class:
public delegate void Position_Changed_Delegate (Geocoordinate c, Boolean logged); public delegate void Status_Changed_Delegate (PositionStatus s); public class DataLogger { Position_Changed_Delegate position_delegate; Status_Changed_Delegate status_delegate; … public void SetCallbackPositionChanged (Position_Changed_Delegate p) { position_delegate= p; } public void SetCallbackStatusChanged(Status_Changed_Delegate s) { status_delegate = s; } … }
And registered in MainPage when that object is initialized:
logger.SetCallbackStatusChanged(update_status); logger.SetCallbackPositionChanged(update_position);
The extra information passed in the Position_Changed_Delegate is whether or not the last received trackpoint was logged by the DataLogger object. This allows me to update the UI display with not only the device’s current position, but also with the number of data points that have been logged to one of our data files (and, as we’ll see later on, whether or not to add it to the visible breadcrumb trail in the map view). This would be difficult to accomplish if the MainPage object registered a PositionChanged event directly as it would need to then query the DataLogger object to get this extra information. This could potentially present a race condition if two PositionChanged events arrived in rapid succession.
← Part 2: User Interface |