Tuesday, November 22, 2016

Draw Path on Google Maps Android API

Draw Path on Google Maps Android API

This Android tutorial is to demonstrate a sample application which will draw path for a route in Google map using Android API v2. This tutorial is a part of Google maps series. I recommend you to go through the previous tutorial Draw lines on Google Maps Android API. This earlier tutorial is to draw straight lines (polyline) between given latitude and longitude.
The example Android app given in this tutorial give opportunity to learn to add markers, zoom to a particular latitude/longitude, draw polylines and draw path along a route in Google map. Pre-requisite is same for all Google maps tutorial and so I am not going to repeat it again, so to setup the development environment, to get the Android API key and related preparations please go through the previous tutorial link given above.
PathGoogleMap
Above shown image is the output of the example Android app. I have chose three locations and drawn path between them. Those three locations are shown by Android markers.

The Google Directions API

To get the directions to draw the path, I have used the Google Directions API. We just hit the given URL using HTTP request and get json response. This Google service calculates direction between given locations.

PathGoogleMapActivity.java

package com.smr.android.maps.path;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.json.JSONObject;

import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;

public class PathGoogleMapActivity extends FragmentActivity {

 private static final LatLng LOWER_MANHATTAN = new LatLng(40.722543,
   -73.998585);
 private static final LatLng BROOKLYN_BRIDGE = new LatLng(40.7057, -73.9964);
 private static final LatLng WALL_STREET = new LatLng(40.7064, -74.0094);

 GoogleMap googleMap;
 final String TAG = "PathGoogleMapActivity";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_path_google_map);
  SupportMapFragment fm = (SupportMapFragment) getSupportFragmentManager()
    .findFragmentById(R.id.map);
  googleMap = fm.getMap();

  MarkerOptions options = new MarkerOptions();
  options.position(LOWER_MANHATTAN);
  options.position(BROOKLYN_BRIDGE);
  options.position(WALL_STREET);
  googleMap.addMarker(options);
  String url = getMapsApiDirectionsUrl();
  ReadTask downloadTask = new ReadTask();
  downloadTask.execute(url);

  googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(BROOKLYN_BRIDGE,
    13));
  addMarkers();

 }

 private String getMapsApiDirectionsUrl() {
  String waypoints = "waypoints=optimize:true|"
    + LOWER_MANHATTAN.latitude + "," + LOWER_MANHATTAN.longitude
    + "|" + "|" + BROOKLYN_BRIDGE.latitude + ","
    + BROOKLYN_BRIDGE.longitude + "|" + WALL_STREET.latitude + ","
    + WALL_STREET.longitude;

  String sensor = "sensor=false";
  String params = waypoints + "&" + sensor;
  String output = "json";
  String url = "https://maps.googleapis.com/maps/api/directions/"
    + output + "?" + params;
  return url;
 }

 private void addMarkers() {
  if (googleMap != null) {
   googleMap.addMarker(new MarkerOptions().position(BROOKLYN_BRIDGE)
     .title("First Point"));
   googleMap.addMarker(new MarkerOptions().position(LOWER_MANHATTAN)
     .title("Second Point"));
   googleMap.addMarker(new MarkerOptions().position(WALL_STREET)
     .title("Third Point"));
  }
 }

 private class ReadTask extends AsyncTask<String, Void, String> {
  @Override
  protected String doInBackground(String... url) {
   String data = "";
   try {
    HttpConnection http = new HttpConnection();
    data = http.readUrl(url[0]);
   } catch (Exception e) {
    Log.d("Background Task", e.toString());
   }
   return data;
  }

  @Override
  protected void onPostExecute(String result) {
   super.onPostExecute(result);
   new ParserTask().execute(result);
  }
 }

 private class ParserTask extends
   AsyncTask<String, Integer, List<List<HashMap<String, String>>>> {

  @Override
  protected List<List<HashMap<String, String>>> doInBackground(
    String... jsonData) {

   JSONObject jObject;
   List<List<HashMap<String, String>>> routes = null;

   try {
    jObject = new JSONObject(jsonData[0]);
    PathJSONParser parser = new PathJSONParser();
    routes = parser.parse(jObject);
   } catch (Exception e) {
    e.printStackTrace();
   }
   return routes;
  }

  @Override
  protected void onPostExecute(List<List<HashMap<String, String>>> routes) {
   ArrayList<LatLng> points = null;
   PolylineOptions polyLineOptions = null;

   // traversing through routes
   for (int i = 0; i < routes.size(); i++) {
    points = new ArrayList<LatLng>();
    polyLineOptions = new PolylineOptions();
    List<HashMap<String, String>> path = routes.get(i);

    for (int j = 0; j < path.size(); j++) {
     HashMap<String, String> point = path.get(j);

     double lat = Double.parseDouble(point.get("lat"));
     double lng = Double.parseDouble(point.get("lng"));
     LatLng position = new LatLng(lat, lng);

     points.add(position);
    }

    polyLineOptions.addAll(points);
    polyLineOptions.width(2);
    polyLineOptions.color(Color.BLUE);
   }

   googleMap.addPolyline(polyLineOptions);
  }
 }
}

HttpConnection.java

package com.smr.android.maps.path;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

import android.util.Log;

public class HttpConnection {
 public String readUrl(String mapsApiDirectionsUrl) throws IOException {
  String data = "";
  InputStream iStream = null;
  HttpURLConnection urlConnection = null;
  try {
   URL url = new URL(mapsApiDirectionsUrl);
   urlConnection = (HttpURLConnection) url.openConnection();
   urlConnection.connect();
   iStream = urlConnection.getInputStream();
   BufferedReader br = new BufferedReader(new InputStreamReader(
     iStream));
   StringBuffer sb = new StringBuffer();
   String line = "";
   while ((line = br.readLine()) != null) {
    sb.append(line);
   }
   data = sb.toString();
   br.close();
  } catch (Exception e) {
   Log.d("Exception while reading url", e.toString());
  } finally {
   iStream.close();
   urlConnection.disconnect();
  }
  return data;
 }

}

PathJSONParser.java

package com.smr.android.maps.path;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.google.android.gms.maps.model.LatLng;

public class PathJSONParser {

 public List<List<HashMap<String, String>>> parse(JSONObject jObject) {
  List<List<HashMap<String, String>>> routes = new ArrayList<List<HashMap<String, String>>>();
  JSONArray jRoutes = null;
  JSONArray jLegs = null;
  JSONArray jSteps = null;
  try {
   jRoutes = jObject.getJSONArray("routes");
   /** Traversing all routes */
   for (int i = 0; i < jRoutes.length(); i++) {
    jLegs = ((JSONObject) jRoutes.get(i)).getJSONArray("legs");
    List<HashMap<String, String>> path = new ArrayList<HashMap<String, String>>();

    /** Traversing all legs */
    for (int j = 0; j < jLegs.length(); j++) {
     jSteps = ((JSONObject) jLegs.get(j)).getJSONArray("steps");

     /** Traversing all steps */
     for (int k = 0; k < jSteps.length(); k++) {
      String polyline = "";
      polyline = (String) ((JSONObject) ((JSONObject) jSteps
        .get(k)).get("polyline")).get("points");
      List<LatLng> list = decodePoly(polyline);

      /** Traversing all points */
      for (int l = 0; l < list.size(); l++) {
       HashMap<String, String> hm = new HashMap<String, String>();
       hm.put("lat",
         Double.toString(((LatLng) list.get(l)).latitude));
       hm.put("lng",
         Double.toString(((LatLng) list.get(l)).longitude));
       path.add(hm);
      }
     }
     routes.add(path);
    }
   }

  } catch (JSONException e) {
   e.printStackTrace();
  } catch (Exception e) {
  }
  return routes;
 }

 /**
  * Method Courtesy :
  * jeffreysambells.com/2010/05/27
  * /decoding-polylines-from-google-maps-direction-api-with-java
  * */
 private List<LatLng> decodePoly(String encoded) {

  List<LatLng> poly = new ArrayList<LatLng>();
  int index = 0, len = encoded.length();
  int lat = 0, lng = 0;

  while (index < len) {
   int b, shift = 0, result = 0;
   do {
    b = encoded.charAt(index++) - 63;
    result |= (b & 0x1f) << shift;
    shift += 5;
   } while (b >= 0x20);
   int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
   lat += dlat;

   shift = 0;
   result = 0;
   do {
    b = encoded.charAt(index++) - 63;
    result |= (b & 0x1f) << shift;
    shift += 5;
   } while (b >= 0x20);
   int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
   lng += dlng;

   LatLng p = new LatLng((((double) lat / 1E5)),
     (((double) lng / 1E5)));
   poly.add(p);
  }
  return poly;
 }
}

activity_path_google_map.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".TaskRoadMap" >

   <fragment
        android:id="@+id/map"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        class="com.google.android.gms.maps.SupportMapFragment"/>

</RelativeLayout>

AndroidManifest.xml



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.smr.android.maps.path"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <permission
        android:name="com.smr.android.maps.path.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.smr.android.maps.path.permission.MAPS_RECEIVE" />    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.smr.android.maps.path.PathGoogleMapActivity"            android:label="@string/title_activity_path_google_map" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="AIzaSyARCTcX8bABCDE_ohscNcALAak-HnjTO5s" />
        
        <meta-data 
            android:name="com.google.android.gms.version" 
            android:value="@integer/google_play_services_version" />

    </application>

</manifest>