Location Data Logger Design and Implementation, Part 4: Bing Maps Integration

This is part 4 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 Bing* Maps SDK

One of Location Data Logger's primary features is the map display which show's the device's current location and a so-called breadcrumb trail of its movement. This is accomplished using the Bing Maps SDK for Windows Store apps (and note that this URL does change, so the preceding link may not work forever. Search engines are your friend). Before I dive into the details of gluing the map display to Location Data Logger, however, it's probably worth covering an important ground rule: the Bing Maps SDK has a license agreement associated with it, and to use the map control in your app you must first obtain a developer key and then agree to the terms and conditions of its use.

Design goals

The first step in integration is to figure out what you want the control to do, and how it should fit in with the overall app. For Location Data Logger I had the following requirements:

  1. The device's current position should always be displayed on the map. Any time a position update occurs, whether or not we are actively logging the data points, the display should update.
  2. The map should auto-center to track the device's movement.
  3. The user can override auto-centering, as well as turn it back on.
  4. The data points that have been logged should also be displayed on the map as a breadcrumb trail.
  5. The user can turn off the breadcrumb display

These will require us to set a number of event handlers for the map control, and I'll cover those implementation details in a moment.

Adding the map control

I used XAML to add the map control to the page layout. The XAML does require you to import the Bing.Maps namespace, however, and here I have mapped Bing.Maps to the prefix bm:.

<common:LayoutAwarePage
 x:Name="pageRoot"
 x:Class="Location_Data_Logger.MainPage"
...
xmlns:bm="using:Bing.Maps"
 

The map control can now be added thusly:

 <bm:Map Grid.Row="0" x:Name="mapPosition" Credentials="INSERT_YOUR_DEVELOPER KEY_HERE"
 PointerWheelChangedOverride="mapPosition_PointerWheelChanged" PointerMovedOverride="mapPosition_PointerMoved"
 DoubleTappedOverride="mapPosition_DoubleTapped" PointerCanceledOverride="mapPosition_PointerCanceled"
 PointerReleasedOverride="mapPosition_PointerReleased" PointerPressedOverride="mapPosition_PointerPressed"
 ViewChanged="mapPosition_ViewChanged" />
 

Note the long list of event handlers which I meantioned briefly, above. Also in there is the Credentials attribute, which is where you place yoru developer key. Your key is generated for you when you create an account for yourself and register your app. Each app you create uses its own, unique key, and this key is used to unqieuly identify your app to the Bing servers. This is how Microsoft tracks your app's usage of the service: if your app's usage exceeds the limits set for free accounts then your app will be blocked-- for anyone who uses it, whevere they are-- until the next day. (If this happens frequently you will want to consider purchasing a high-volume license.)

Displaying the device position

Display position on the map is pretty straightforward, but it does require setting up some infrastructure. And to do that, I need to frist talk about the structure of the map control, itself. The map object has, among other things, two properties that hold map objects:

  1. The Children property is a MapUIElementCollection object which is used to hold UI elements directly, and MapLayer objects if you choose to create layers of controls. If you create a custom control for placement on the map, as I do in Location Data Logger, you will add it to this collection.
  2. The ShapeLayer property is a MapShapeLayerCollection object which holds MapShapeLayer objects. A MapShapeLayer is where you draw map shapes (polylines, polygons, and multipoints).

To display the device's position, I created a user control named MapAccuracyCircle. The specifics of this control will be discussed in a future post in this blog series, but for now just accept that this is a custom control, and as a control it gets added to the map's Children collection. Displaying the location on the map means placing the position into an object that the Bing Maps control can understand, and then updating that position as needed.

The relevant code snippets:

 public sealed partial class MainPage : Location_Data_Logger.Common.LayoutAwarePage
 {
 ...
MapAccuracyCircle accuracy_circle;
 Location current_location;
...
public MainPage()
 {
...
accuracy_circle = new MapAccuracyCircle(mapPosition);
 accuracy_circle.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
 mapPosition.Children.Add(accuracy_circle);

Note that I start with the Visibility property set to Collapsed. This is because there is no location set yet, so the map control should not be visible. Visibility is set in the update_status() method:

accuracy_circle.Visibility = (s == Windows.Devices.Geolocation.PositionStatus.Ready) ?
 Windows.UI.Xaml.Visibility.Visible : Windows.UI.Xaml.Visibility.Collapsed;

The Location class is part of the Bing Maps API and contains the coordinates of a location on the map, and it is updated within the update_position() method:

MapLayer.SetPosition(accuracy_circle, current_location);

(Using MapLayer in this manner is how you adjust the position of a control that has been directly added to a map instead of through a map layer.)

Auto-centering on the device location

Auto-centering is quite simple. Every time a PositionChange event is received, just recenter the map.

if ( toggleAutoCenter.IsChecked == true ) mapPosition.SetView(current_location, map_zoom);

If auto-centering is enabled, via a toggle button, then set the new map view. The map's zoom level is tracked in the variable map_zoom in case it is needed elsewhere in the future (currently, this variable is redundant and not really used).

Toggling auto-center off and on

It makes sense to disable auto-centering of the map when the user takes action that implies he or she no longer wants it, such as scrolling the map view, and to turn it back on when they are done. This is accomplished in two ways. The first is with a toggle button below the map called "Autocenter". When it's on, the map display will auto-center and when it's off, it won't. The second is using event handlers on the map control.

 // Map update events

private void mapPosition_PointerPressed(object sender, RoutedEventArgs e)
{
    map_pointer_pressed = true;
}

private void mapPosition_PointerReleased(object sender, RoutedEventArgs e)
{
    map_pointer_pressed = false;
}

private void mapPosition_PointerMoved(object sender, RoutedEventArgs e)
{
    if (map_pointer_pressed == true)
    {
       toggleAutoCenter.IsChecked = false;
    }
}

private void mapPosition_PointerCanceled(object sender, RoutedEventArgs e)
{
    map_pointer_pressed = false;
}

private void mapPosition_DoubleTapped(object sender, RoutedEventArgs e)
{
    toggleAutoCenter.IsChecked = false;
}

private void mapPosition_ViewChanged(object sender, ViewChangedEventArgs e)
{
    map_zoom = mapPosition.ZoomLevel;
}

private void mapPosition_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
    toggleAutoCenter.IsChecked = false;
}

There's a lot going on here, and it breaks down like this. Auto-centering is disabled when any of the following occur:

  1. The map view changes. ViewChanged event is generated after the map view change has completed. Meaning, if you scroll the map view, the event is generated when the scrolling stops.
  2. The map is double-clicked. The DoubleTapped event is used to zoom in on the map. This action always changes the map's center point unless the user just happens to double click on the exact pixel in the middle of the map. (This is not too likely, and it is a case that is ignored.)
  3. The pointer is moved while the pointer is pressed. This is the classic "click and drag" motion. Note that the added complex logic here is to prevent auto-centering from being disabled just because a mouse pointer moves across the map. The user must explicitly be doing a click-and-drag. Auto-center is immediately turned off as soon as a drag operation starts. This is to prevent the map from auto-centering while the user is actively manipulating it.
  4. The pointer wheel is changed. This refers to the wheel device on a mouse. This action is mapped to zooming in and out in the map control. See #2.

The Breadcrumb Trail

The breadcrumb trail, which shows the positions recorded by the app during logging, is displayed using a shape layer. Two objects are added to the MainPage class

MapShapeLayer layerBreadcrumb;
 MapPolyline lineBreadcrumb;

and the following code in the MainPage() constructor gets the map initialized:

lineBreadcrumb = new MapPolyline();
layerBreadcrumb = new MapShapeLayer();

lineBreadcrumb.Color = Windows.UI.Colors.CornflowerBlue;
lineBreadcrumb.Width = 3;

layerBreadcrumb.Shapes.Add(lineBreadcrumb);
mapPosition.ShapeLayers.Add(layerBreadcrumb);

The breadcrumb trail is incrementally built inside of the update_position() delegate. When update_position() is called, the DataLogger object also includes the boolean parameter logged. If this value is true, then we add the point to the lineBreadcrumb polyline.

current_location = new Location(c.Latitude, c.Longitude);
if (logged)
{

    ...

    // Add the point to our breadcrumb trail
    if (lineBreadcrumb != null) lineBreadcrumb.Locations.Add(current_location);
}

Since the breadcrumb display only shows the currently logged points, it also has to be cleared whenever the user starts a new logging session. This is accomplished in the logger_start() method.

lineBreadcrumb.Locations.Clear();


Turning the breadcrumb display on and off

This is done with a toggle button under the map.

private void toggleBreadcrumb_Click(object sender, RoutedEventArgs e)
{
    Boolean onoff = (Boolean)toggleBreadcrumb.IsChecked;

    lineBreadcrumb.Visible = onoff;
}

Note that I'm not just changing the Visible property here: I am also saving the display preference for future sessions by writing it to roamingSettings.

← Part 3: The DataLogger Class Part 5: The Data Grid View →
Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.