Xamarin.iOS Google Maps Directions

In an earlier post I showed how to use the new MKDirections API in iOS 7 to display directions. This post will show how to use Google’s Directions API to plot directions based on locations tapped by the user. Using Google maps for directions can be beneficial for those who are located where Apple directions are unavailable.

The source code for this example is available on GitHub.

First, you’ll need to generate a new API key. Instructions on getting started as well as generating a new API key can be found here¬†.

Next, you will need to add a reference to googlemaps. I’ve found that the easiest way to do this is through the component store.

We’ll also be using jSON.net to parse the route data. This can be found in the component store as well.

Be sure and set the identifier under the iOS Application tab in the project properties. This should be the same name you used when generating the API key.

In your AppDelegate you will need to provide your API key to the Map Services.

MapServices.ProvideAPIKey(ApiKey);

This example has a single view that is a map. The map has one button to clear the contents of the map (Markers and Polylines), and one button to zoom to the users current location.

The following is added to the ViewDidLoad of the main view controller.

base.ViewDidLoad();
View.Frame = UIScreen.MainScreen.Bounds;
 View.BackgroundColor = UIColor.White;
 View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
SetupMap();
//Setup button to clear map
 var btn = new UIButton(new RectangleF(0, 0, 100, 50));
 btn.Layer.BorderWidth = 1;
 btn.Layer.CornerRadius = 5;
 btn.Layer.BorderColor = UIColor.Black.CGColor;
 btn.SetTitle(@"Clear Map", UIControlState.Normal);
 btn.BackgroundColor = UIColor.Gray;
 btn.TouchUpInside += (s, e) =>
 {
 _mapView.Clear();
 _mapDelegate.Lines.Clear();
 _mapDelegate.Locations.Clear();
 };

 _mapView.AddSubview(btn);

And the SetupMap Method…

private void SetupMap()
 {
//Init Map wiht Camera
 var camera = new CameraPosition(new CLLocationCoordinate2D(36.069082, -94.155976), 15, 30, 0);
 _mapView = MapView.FromCamera(RectangleF.Empty, camera);
 _mapView.MyLocationEnabled = true;
//Add button to zoom to location
 _mapView.Settings.MyLocationButton = true;
_mapView.MyLocationEnabled = true;
 _mapView.MapType = MapViewType.Hybrid;
 _mapView.Settings.SetAllGesturesEnabled(true);
//Init MapDelegate
 _mapDelegate = new MapDelegate(_mapView);
 _mapView.Delegate = _mapDelegate;
View = _mapView;
 }

_mapDelegate is an instance of a class that inherits from MapViewDelegate. We’ve called our class MapDelegate. This class will override the DidTapAtCoordinate method. In this method we will add a new marker to the map. If there are at least two markers we will begin building a url to get directions from the Directions API.

public override void DidTapAtCoordinate(MapView mapView, CLLocationCoordinate2D coordinate)
{
//Create/Add Marker 
 var marker = new Marker { Position = coordinate, Map = mapView };
 Locations.Add(coordinate);
 if (Locations.Count > 1)
 {
   SetDirectionsQuery();
 }
}

Our example uses the first tap as the origin, the second as the destination, and everything in between is a waypoint. We request that google returns the directions in jSON format. I’ve created the classes below to represent the route data that’s returned.

public class Northeast
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Southwest
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Bounds
{
 public Northeast northeast { get; set; }
 public Southwest southwest { get; set; }
}
public class Distance
{
 public string text { get; set; }
 public int value { get; set; }
}
public class Duration
{
 public string text { get; set; }
 public int value { get; set; }
}
public class EndLocation
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class StartLocation
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Distance2
{
 public string text { get; set; }
 public int value { get; set; }
}
public class Duration2
{
 public string text { get; set; }
 public int value { get; set; }
}
public class EndLocation2
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Polyline
{
 public string points { get; set; }
}
public class StartLocation2
{
 public double lat { get; set; }
 public double lng { get; set; }
}
public class Step
{
 public Distance2 distance { get; set; }
 public Duration2 duration { get; set; }
 public EndLocation2 end_location { get; set; }
 public string html_instructions { get; set; }
 public Polyline polyline { get; set; }
 public StartLocation2 start_location { get; set; }
 public string travel_mode { get; set; }
 public string maneuver { get; set; }
}
public class Leg
{
 public Distance distance { get; set; }
 public Duration duration { get; set; }
 public string end_address { get; set; }
 public EndLocation end_location { get; set; }
 public string start_address { get; set; }
 public StartLocation start_location { get; set; }
 public List<Step> steps { get; set; }
 public List<object> via_waypoint { get; set; }
}
public class OverviewPolyline
{
 public string points { get; set; }
}
public class Route
{
 public Bounds bounds { get; set; }
 public string copyrights { get; set; }
 public List<Leg> legs { get; set; }
 public OverviewPolyline overview_polyline { get; set; }
 public string summary { get; set; }
 public List<object> warnings { get; set; }
 public List<object> waypoint_order { get; set; }
}
public class RootObject
{
 public List<Route> routes { get; set; }
 public string status { get; set; }
}

The SetDirectionsQuery method is where we will spin through the locations associated with each marker, and begin building our URL. We then Deserialize the returned string into an object. We’ll spin through the collection of routes returned, and for each route use the points property in¬†overview_polyline on each route to produce a path, which is then used to create a polyline. The polylines map property is set, and this places it on the map.

private async void SetDirectionsQuery()
 {
 //Clear Old Polylines
 if (Lines.Count > 0)
 {
 foreach (var line in Lines)
 {
 line.Map = null;
 }
 Lines.Clear();
 }
//Start building Directions URL
 var sb = new System.Text.StringBuilder();
 sb.Append(KMdDirectionsUrl);
 sb.Append(Locations[0].Latitude.ToString(CultureInfo.InvariantCulture));
 sb.Append(",");
 sb.Append(Locations[0].Longitude.ToString(CultureInfo.InvariantCulture));
 sb.Append("&");
 sb.Append("destination=");
 sb.Append(Locations[1].Latitude.ToString(CultureInfo.InvariantCulture));
 sb.Append(",");
 sb.Append(Locations[1].Longitude.ToString(CultureInfo.InvariantCulture));
 sb.Append("&sensor=true");
//If we have more than 2 locations we'll append waypoints
 if (Locations.Count > 2)
 {
 sb.Append("&waypoints=");
 for (var i = 2; i < Locations.Count; i++)
 {
 if (i > 2)
 sb.Append("|");
 sb.Append(Locations[i].Latitude.ToString(CultureInfo.InvariantCulture));
 sb.Append(",");
 sb.Append(Locations[i].Longitude.ToString(CultureInfo.InvariantCulture));
 }
 }
//Get directions through Google Web Service
 var directionsTask = GetDirections(sb.ToString());
var jSonData = await directionsTask;
//Deserialize string to object
 var routes = JsonConvert.DeserializeObject<RootObject>(jSonData);
foreach (var route in routes.routes)
 {
 //Encode path from polyline passed back
 var path = Path.FromEncodedPath(route.overview_polyline.points);
//Create line from Path
 var line = Google.Maps.Polyline.FromPath(path);
 line.StrokeWidth = 10f;
 line.StrokeColor = UIColor.Red;
 line.Geodesic = true;
//Place line on map
 line.Map = _map;
 Lines.Add(line);
}
}
private async Task<String> GetDirections(string url)
 {
 var client = new WebClient();
 var directionsTask = client.DownloadStringTaskAsync(url);
 var directions = await directionsTask;
return directions;
}
Google Maps Directions

Google Maps Directions