Asset Management #2 - Selecting a location

by CameronM 12. January 2013 11:56

In this post we’ll continue building an Asset Management app that enables a field worker to selected a point on the map and lodge a new defect.

In the first post we looked at how we can centre the map at the devices current location. In this post, we’ll add functionality to handle when the user selects a location on the map.

We want to do two things when the user selects a location. Firstly, we want to show a point on the map and secondly we want to store the street address. We’ve covered most of this code in previous posts, but here we’ll put it all together in a real-world scenario.

There are a number of ways to display a UI element on the Map control, including programmatically via code-behind and in XAML. In both cases, we’ll use the Pushpin class that comes with the Windows Phone Toolkit.

Adding a Pushpin Programmatically

To add a pushpin to the Map control programmatically we’ll need to declare a couple of local variables for a Pushpin, MapOverlay and MapLayer. We’ll create a new map overlay that is displayed on a map layer when the page first loads, then we’ll display a Pushpin to it when the user taps the map. As we only want one Pushpin, we’ll reset the location of the Pushpin every time the user taps the maps, rather than creating a new Pushpin.

// variables for displaying the pushpin
Pushpin pin = new Pushpin();
MapOverlay overlay = new MapOverlay();
MapLayer layer = new MapLayer();
 
// local variable to store the selected location
MapLocation defectLocation;

The next step is to add a handler for the Tap event for the Map control. This handler needs to place a Pushpin on the Map and then call the ReverseGeocodeQuery to get the street address.

<maps:Map x:Name="LocationMap" Tap="LocationMap_Tap"  />
private void LocationMap_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
    //get the location of the Tap
    GeoCoordinate location = LocationMap.ConvertViewportPointToGeoCoordinate(e.GetPosition(LocationMap));
 
    //show a pushpin on the map
    pin.GeoCoordinate = location;
 
    //set the overlay location
    overlay.GeoCoordinate = location;
 
    //add the pushpin to the overlay
    overlay.Content = pin;
 
    //reverse geocode the location
    ReverseGeocodeQuery reverse = new ReverseGeocodeQuery();
    reverse.GeoCoordinate = location;
    reverse.QueryCompleted += reverse_QueryCompleted;
    reverse.QueryAsync();
}

When the ReverseGeocodeQuery completes, we’ll store the address and suburb details in a local variable. This value, along with the location of the Pushpin could then be saved along with any other relevant details, to create a new asset defect.

void reverse_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
    // store the address in a local variable
    if(e.Error==null)
    {
        //grab the first location
        if (e.Result.Count() > 0)
        {
            defectLocation = e.Result.FirstOrDefault();
        }        
    }
}

Modify the OnNavigatedTo event to include code to add our overlay to the layer and the layer to the map.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    // get current location
    Geolocator locator = new Geolocator();
    Geoposition position = await locator.GetGeopositionAsync();
    var location = position.Coordinate.ToGeoCoordinate();
 
    // center the map at the current location
    LocationMap.Center = location;
    LocationMap.ZoomLevel = 18;
 
    //the map overlay will hold the pushpin
    overlay.PositionOrigin = new Point(0, 1);
 
    //add the overlay to the layer 
    layer.Add(overlay);
 
    //add the layer to the map
    LocationMap.Layers.Add(layer);
 
    base.OnNavigatedTo(e);
}

The code is pretty self-explanitory, except perhaps why we're setting the overlay.PositionOrigin. If you comment out this line, you'll notice that the Pushpin displays a little below the location you clicked on the map. Setting the origin of the overlay rectifies this.

Test the app and when you tap on the app, the pushpin will be displayed. Tap again in a different location and the pushpin will be moved to that location.

Tags: , ,

Windows Phone 8 | WP8

Asset Management #1 - Getting users location

by CameronM 10. January 2013 17:38

While building a recent asset management app for mobile field staff, I had to create a page where they could lodge defects. Part of the lodgement process required them to select the location of the defect (latitude/longitude) and enter the physical street address.

The original HTML5 app that I built used the javascript version of Nokia HERE maps that enabled the field staff to perform these tasks, but moving to Windows Phone 8 meant that this could now be handled by the new API features.

The first step to achieve this is to add a Map control to your XAML page, remembering to include the Microsoft.Phone.Maps.Control XML namespace in the page declaration.

xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"
<maps:Map x:Name="LocationMap" />

When the user opened the page, I wanted the map to zoom to their current location to make it as quick as possible to select the asset that needed fixing. To do this, we need to initialise a Geolocator, which can be used to return the current location.

using Windows.Devices.Geolocation;

Override the OnNavigatedTo to get the current location and center the map

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    // get current location
    Geolocator locator = new Geolocator();
    Geoposition position = await locator.GetGeopositionAsync();
    var location = position.Coordinate.ToGeoCoordinate();
 
    // center the map at the current location
    LocationMap.Center = location;
    LocationMap.ZoomLevel = 18;
 
    base.OnNavigatedTo(e);
}

One catch with this process is that the classes used to represent a location from the Geolocator and the Map are similar, but actually different. The Geolocator returns a Windows.Devices.Geolocation.Geocoordinate objects, but to use this on a map we need a System.Device.Location.GeoCoordinate.

While it’s possible to write your own converter, the Windows Phone Toolkit provides a handy map extension that does the conversion for us. You’ll need to add a reference to the Toolkit in your code-behind.

using Microsoft.Phone.Maps.Toolkit;

Then you can call the ToGeoCoordinate() extension method of the Geocoordinate class.

Before testing your app, be sure to set the ID_CAP_LOCATION capability for you app in the WMAppManifest or you'll get an exception.

Tags: , ,

Windows Phone 8 | WP8

Windows Phone 8 - The new map services

by CameronM 10. December 2012 17:11

One of the really nice enhancements that have been made to the mapping capabilities in the Windows Phone 8 SDK is the addition of services such as geocoding and reverse geocoding, which are functions that are often required when building map-based solutions.

I wrote a post some time ago showing how you could utilise the Bing Maps API to perform these tasks, but in WP8, they’re built in to the SDK. GeocodeQuery and ReverseGeocodeQuery are two classes in the new Microsoft.Phone.Maps.Services namespace that provide these function. 

GeocodeQuery

Geocoding is the process of retrieving a geographical location (latitude/longitude) when provided a physical address, such as a street and suburb name. You’d use geocoding in your app if you want users to enter an address in a textbox and then see that displayed on a map.

The code to perform a GeocodeQuery couldn't be much easier. Assuming you have a TextBox name SearchTextBox where the user will enter the address to query and a Button that they press, your code to perform the query will look as simple as this.

private void SearchButton_Click(object sender, RoutedEventArgs e)
{
    GeocodeQuery query = new GeocodeQuery();
    query.SearchTerm = SearchTextBox.Text;
    query.GeoCoordinate = new System.Device.Location.GeoCoordinate(-27.5, 153);
    query.QueryCompleted += query_QueryCompleted;
    query.QueryAsync();
}

You will need to decide how to handle the results of the query, which if successful will be a list of MapLocation objects. For this example we'll just display the street address of the first record, but obviously you could display the results in a list and let the user select the relevant record.

void query_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
    //check that the query didn't raise an exception
    if(e.Error==null)
    {
        //grab the first location//
        if (e.Result.Count() > 0)
        {
            var location = e.Result.FirstOrDefault();
            MessageBox.Show(location.Information.Address.Street);
        }
    }
}

The MapLocation class contains a number of properties, including GeoCoordinate (latitide/longitude) and Information. The Information property returns a LocationInformation object that can be used to retrieve values such as the Address, Description and Name of the location. Depending on the addres that was entered, not all of these values will be populated. For example, if the user enters a regular suburban address, only the Address property is populated.

ReverseGeocodeQuery

As the name suggests, reverse geocoding is the process of retrieving a physical address (street/suburb) when provided a geographical location (latitude/longitude). You’d use reverse-geocoding in your app if you wanted users to be able to tap a point on a map and see what the address is.

To illustrate the code to perform a ReverseGeocodeQuery, we’ll need a simple UI that allows a user to select a location on a map. You can either add a map via code or in XAML, but we’ll use XAML for this example.

Firstly add the XML namespace to your page declaration. 

xmlns:maps="clr-namespace:Microsoft.Phone.Maps.Controls;assembly=Microsoft.Phone.Maps"

Add a map control and set an appropriate Center and ZoomLevel and add the Tap event handler. 

<maps:Map x:Name="LocationMap" Center="-27.5, 153" ZoomLevel="10" Tap="LocationMap_Tap"  />

Next we’ll need to handle the Tap event, which will be triggered when the user taps the map. 

private void LocationMap_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
    //get the location of the Tap
    GeoCoordinate location = LocationMap.ConvertViewportPointToGeoCoordinate(e.GetPosition(LocationMap));
 
    //reverse geocode the location
    ReverseGeocodeQuery reverse = new ReverseGeocodeQuery();
    reverse.GeoCoordinate = location;
    reverse.QueryCompleted += reverse_QueryCompleted;
    reverse.QueryAsync();
}

Lastly, you'll want to handle the callback from when the query completes. This could be as simple or as complex as you like, but for this example we'll just show the street name of the location.

void reverse_QueryCompleted(object sender, QueryCompletedEventArgs<IList<MapLocation>> e)
{
    // do something with the address
    if(e.Error==null)
    {
        //grab the first location
        if (e.Result.Count() > 0)
        {
            var location = e.Result.FirstOrDefault();
            MessageBox.Show(location.Information.Address.Street);
        }        
    }
}

Tags: , ,

Windows Phone 8 | WP8

Windows Phone 8 - The new map tasks

by CameronM 5. December 2012 20:28

With the release of Windows Phone 8, Microsoft has added a new map control and related mapping tasks. Windows Phone 7 used the BingMapControl and provided a number of Tasks that provided access to the built-in maps app from within your app. I discussed the BinMapsTask and the BingMapsDirectionTask in a couple of previous posts.

While still available in your Windows Phone 8 apps, the Bing-flavoured map control and tasks are deprecated and should not be used for any new development.

Thankfully, replacing the Task is an extremely process and they provide the exact same functionality.

The Maps task opens the built-in map app at a specified location and can be used to search for local results, such as restaurants or coffee houses. You call this function in the exact same way as the old BingMapsTask.

//the old
//BingMapsDirectionsTask dir = new BingMapsDirectionsTask();
//dir.End = new LabeledMapLocation("Address", new GeoCoordinate(-27.3, 152.9));
//dir.Show();
 
//the new
MapsDirectionsTask dir = new MapsDirectionsTask();
dir.End = new LabeledMapLocation("Address"new GeoCoordinate(-27.3, 152.9));
dir.Show();

The Maps directions task can be called from your app and launches the built-in maps app to provide directions to a specified location. You call this function in the exact same way as the old BingMapsDirectionTask.

//the old
//BingMapsTask search = new BingMapsTask();
//search.Center = new GeoCoordinate(-27.5, 153);
//search.SearchTerm = "coffee";
//search.Show();
 
//the new
MapsTask search = new MapsTask();
search.Center = new GeoCoordinate(-27.5, 153);
search.SearchTerm = "coffee";
search.Show();

Tags: , , ,

Windows Phone 8 | WP8

Adding a Map to your WP7 App

by CameronM 1. November 2011 06:08

My recent TrafficMATE Windows Phone 7 app heavily utilised the Bing Maps control for WP7. It was used to plot route in combination with the Bing Maps Route Service API, as well as to show the location of traffic cameras.

The easiest way to get started with the Maps control for Windows Phone is to take a look at the tutorials found in the Labs provided by Microsoft. If you just want to throw up a map to display a single geographic point, then here’s how to start.

Add the control to your xaml

The Map control resides in the Microsoft.Phone.Controls.Maps library, so you will want to add a reference to this library into your project. You will also want to create a namespace reference in the XAML page where you want to use the map control.

xmlns:msmap="clr-namespace:Microsoft.Phone.Controls.Maps;assembly=Microsoft.Phone.Controls.Maps"

You can then add the map control to your xaml.

<msmap:Map x:Name="routeMap"

           CredentialsProvider="{Binding CredentialsProvider}">

</msmap:Map>

 

Set your Bing Maps API key

To use the map you’ll need to get a key, which is free from Microsoft. Without a key, you will be stuck with the message Invalid Credentials Sign up for a developer account.

The API key is a single string, much like you may be familiar with if you have used the Google Maps API. The Map control makes it a little harder though and expects an object of the CredentialsProvider class. It’s easy enough to create a property in your .cs file that exposes a property of type CredentialsProvider and then bind that property to the map property.

private readonly CredentialsProvider _credentialsProvider = new ApplicationIdCredentialsProvider("Your Key");

 

public CredentialsProvider CredentialsProvider

{

    get { return _credentialsProvider; }

}

Adding a pushpin to the map

The first thing you will want to do with your newly acquired map is to zoom to a certain location and display a pushpin.  Thankfully there are a few ways to do this. The first is to programmatically add a pushpin to the map in your .cs file and zoom in.

The bare-bones approach to add a pushpin directly to the Map in your code is shown below.
Pushpin p = new Pushpin();
p.Location = new System.Device.Location.GeoCoordinate(51.42153, -0.20786);
 
//add the pushpin to the map
routeMap.Children.Add(p);

Compile and run the app and you will get a single pushpin, as shown below.

Just as you can add the pushpin in code-behind, you can also programmatically zoom the map to the chosen location, by setting the map’s Center and ZoomLevel properties. Set the zoom to an appropriate number between 1 and 19, where 19 is very close.

//set the center of the map and the zoom level

routeMap.Center = new System.Device.Location.GeoCoordinate(51.42153, -0.20786);

routeMap.ZoomLevel = 19;

Tags: , ,

Windows Phone 7 | WP7