August 23, 2015

Draw path between two locations in google maps v2 xamarin android

In brief: Google maps android API V2 allows the developer to integrate and customize the google map look and feel. This API handles the access to Google Maps Server,fetching map data ,display and response to map gesture.
[read more : https://developers.google.com/maps/documentation/android/intro]
Here I will write the steps to draw path between two geo-location in android xamarin.

In my previous post shared my thought about  Best Practice and issues with ListView in Android XamarinHow to use Google Place API with Autocomplete in Xamarin AndroidIntegrating Login by Google Account in Xamarin.Android,How to avoid ImageBitmap OutOfMemoryException and Rounded corner Image in android Xamarin.

In detail :
To draw the custom path between two geo location,need to make use of the following map API component.
Markers : Icon on defined position
Polylines: Line connecting the defined position [reference: https://developers.google.com/maps/documentation/javascript/reference?hl=en#Polyline]
Polyline are created by passing PolylineOptions which intern specifies the path of the polyline and stroke style of the polyline.
PolylineOptions provides the important stroke style methods[reference : https://developers.google.com/maps/documentation/javascript/reference?hl=en#PolylineOptions]

In steps :
1. Integrate Google map,follow the this post[Build Google Map V2 in Xamarin Android]
GoogleMap map;
var frag = FragmentManager.FindFragmentById<mapfragment>(Resource.Id.map); 
 var mapReadyCallback = new OnMapReadyClass(); 
 mapReadyCallback.MapReadyAction += delegate(GoogleMap obj )
 {
  map=obj;  
  FnProcessOnMap();
 };   
 frag.GetMapAsync(mapReadyCallback);

 public class OnMapReadyClass :Java.Lang.Object,IOnMapReadyCallback
 { 
  public GoogleMap Map { get; private set; } 
  public event Action<googlemap> MapReadyAction;
  public void OnMapReady (GoogleMap googleMap)
  {
   Map = googleMap;
   if ( MapReadyAction != null )
    MapReadyAction (Map);
  }
 }
[Note: Avoid Gms.Maps.MapFragment.Map which is deprecated]

2.Register in Google developer console[https://console.developers.google.com]
2.1. In left menu select "APIs and Auth" and search for direction api and select and enable it.


2.2. In left menu select Credential. Click "Add credential"->"API key". In a popped up "create a new key" window,select "server key" and press "Create".



2.3. Note the server key created,same should be used in direction api request in below step.

3.Setup source and destination point.
Here i have taken two random location's,you can also provide auto-complete location using google api.
Convert that location into geo coordinates[lat,long]. This can be done in two ways using android native method[Geocoder()] or by using google geocoder api.

async Task<bool> FnLocationToLatLng()
  {
   try
   {
   var geo = new Geocoder ( this );
   var sourceAddress =await geo.GetFromLocationNameAsync(Constants.strSourceLocation , 1 );
   sourceAddress.ToList ().ForEach ((addr) => {
    latLngSource =new LatLng(addr.Latitude,addr.Longitude);
   } );

    var destAddress =await geo.GetFromLocationNameAsync(Constants.strDestinationLocation , 1 );
   destAddress.ToList ().ForEach ((addr) => {
    latLngDestination =new LatLng(addr.Latitude,addr.Longitude);
   } );

   return true;
   }
   catch
   {
    return false;
   }
  }


To use Google geocode api to convert location to coordinate follow this http://appliedcodelog.blogspot.in/2015/08/using-google-geocoding-and-reverse.html

3.Update the camera position.
To highlight the path properly,Need to zoom the map to the selected location. Here i will update camera to a source location.

 void FnupdateCameraPosition(LatLng pos)
  {
   CameraPosition.Builder builder = CameraPosition.InvokeBuilder();
   builder.Target(pos);
   builder.Zoom(12);
   builder.Bearing(45);
   builder.Tilt(10);
   CameraPosition cameraPosition = builder.Build();
   CameraUpdate cameraUpdate = CameraUpdateFactory.NewCameraPosition(cameraPosition);
   map.AnimateCamera(cameraUpdate);
  }

 4.Google direction API HttpWebRequest and parse the json response.
 Make a simple REST request [How to make REST request and JSON parsing] to google direction api along with the server key created in the step2. [read about the optional parameter and more https://developers.google.com/maps/documentation/directions/intro]
 Parse the JSON response to class object.

 internal static string strGoogleDirectionUrl="https://maps.googleapis.com/maps/api/directions/json?origin=src_locn&destination=dest_lcn&key=keyGoesHere";
 string strJSONDirectionResponse = await FnHttpRequest(strGoogleDirectionUrl);
 if ( strJSONDirectionResponse != Constants.strException )
   {
//mark source and destination point
  MarkOnMap(Constants.strTextSource,latLngSource,Resource.Drawable.MarkerSource);
  MarkOnMap(Constants.strTextDestination,latLngDestination,Resource.Drawable.MarkerDest);
   }
   var objRoutes = JsonConvert.DeserializeObject<googledirectionclass>( strJSONDirectionResponse );


  WebClient webclient;
  async Task<string> FnHttpRequest(string strUri)
  {
   webclient = new WebClient ();
   string strResultData;
   try
   {
    strResultData= await webclient.DownloadStringTaskAsync (new Uri(strUri));
    Console.WriteLine(strResultData);
   }
   catch
   {
    strResultData = Constants.strException;
   }
   finally
   {
    if ( webclient!=null )
    {
     webclient.Dispose ();
     webclient = null;
    }
   }

   return strResultData;
  }


  void MarkOnMap(string title,LatLng pos, int resourceId )
  {
   var marker = new MarkerOptions ();
   marker.SetTitle ( title );
   marker.SetPosition ( pos ); //Resource.Drawable.BlueDot
   marker.SetIcon ( BitmapDescriptorFactory.FromResource ( resourceId ) );
   map.AddMarker ( marker );
  }

 5.Decode the overview_polyline.points.
 For the security purpose google direction api returns the points along the path in a encoded type.
 Below used Decoding method returns the points in List of type Location. Which is need to be converted into Array of type LatLng to input to PolylineOptions.
 Here "objRoutes" may contain more then one possible route(objRoutes.routes.Count) between source and destination, I will take here the default one[i.e. index 0].

string encodedPoints = objRoutes.routes [0].overview_polyline.points;
 var lstDecodedPoints = FnDecodePolylinePoints ( encodedPoints );
 //convert list of location point to array of latlng type
 var latLngPoints = new LatLng[lstDecodedPoints.Count];
 int index = 0;
 foreach ( Location loc in lstDecodedPoints )
 {
  latLngPoints [index++] = new LatLng ( loc.lat , loc.lng );
 }

 //function to decode,encoded points
  List<location> FnDecodePolylinePoints(string encodedPoints)
  {
   if ( string.IsNullOrEmpty ( encodedPoints ) )
    return null;
   var poly = new List<location>();
   char[] polylinechars = encodedPoints.ToCharArray();
   int index = 0;

   int currentLat = 0;
   int currentLng = 0;
   int next5bits;
   int sum;
   int shifter;

    while (index < polylinechars.Length)
    {
     // calculate next latitude
     sum = 0;
     shifter = 0;
     do
     {
      next5bits = (int)polylinechars[index++] - 63;
      sum |= (next5bits & 31) << shifter;
      shifter += 5;
     } while (next5bits >= 32 && index < polylinechars.Length);

     if (index >= polylinechars.Length)
      break;

     currentLat += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1);

     //calculate next longitude
     sum = 0;
     shifter = 0;
     do
     {
      next5bits = (int)polylinechars[index++] - 63;
      sum |= (next5bits & 31) << shifter;
      shifter += 5;
     } while (next5bits >= 32 && index < polylinechars.Length);

     if (index >= polylinechars.Length && next5bits >= 32)
      break;

     currentLng += (sum & 1) == 1 ? ~(sum >> 1) : (sum >> 1);
     Location p = new Location();
     p.lat = Convert.ToDouble(currentLat) / 100000.0;
     p.lng = Convert.ToDouble(currentLng) / 100000.0;
     poly.Add(p);
    }
 
   return poly;
  }

6.Instanitiate PolylineOptions to shape the path.
Input the latLngPoints created, here setting "Geodesic" true bring the path with more curved structure.
Add the polylineoption instance to map.

var polylineoption = new PolylineOptions ();
polylineoption.InvokeColor ( Android.Graphics.Color.Red );
polylineoption.Geodesic ( true );
polylineoption.Add ( latLngPoints );
map.AddPolyline ( polylineoption );

This is how we can draw a path between two geo-location.
Usage Limit: In the Google map direction document mentioned the below usage limit as of below date.
->2500 directions requests per 24 hour period.
->Up to 8 way points allowed in each request. Waypoints are not available for transit directions.
->2 requests per second

Explore the complete code at github:https://github.com/suchithm/DrawMapPathXamarin/ .

Hope you had liked this post & write your thoughts/suggestion if any below.

 Happiee coding,keep visiting :)