In this article, we will develop an Android application which displays driving distance and travel time between two locations in Google Map Android API V2. It also draws poly line along driving route from start location to end location.
Screenshot of this application is shown in the Figure 7 below.
This application makes use of Google Map Android API V2 and Google Directions API.
This application is developed in Eclipse (4.2.1) with ADT plugin (21.1.0) and Android SDK (21.1.0) and tested in a real Android device (Android 2.3.6 – GingerBread).
1. Create a new Android application project namely “LocationDistanceTimeMapV2″
Figure 1 : Create new Android application project
2. Configure the project
Figure 2 : Configure the application project
3. Design application launcher icon
Figure 3 : Design application launcher icon
4. Create a blank activity
Figure 4 : Create a blank activity
5. Enter MainActivity details
Figure 5 : Enter MainActivity details
6. Download and configure Google Play Services Library in Eclipse
Google Map for Android is now integrated with Google Play Services. So we need to set up Google Play Service Library for developing Google Map application in Android.
Please follow the given below link to setup Google Play Service library in Eclipse.
http://developer.android.com/google/play-services/setup.html
7. Add Google Play Services Library to this project
Figure 6 : Linking Google Play Services Library to this project
8. Get the API key for Google Maps Android API V2
We need to get an API key from Google to use Google Maps in Android application.
Please follow the given below link to get the API key for Google Maps Android API v2.
https://developers.google.com/maps/documentation/android/start
9. Add Android Support library to this project
By default, Android support library (android-support-v4.jar ) is added to this project by Eclipse IDE to the directory libs. If it is not added, we can do it manually by doing the following steps :
- Open Project Explorer by Clicking “Window -> Show View -> Project Explorer”
- Right click this project
- Then from popup menu, Click “Android Tools -> Add Support Library ”
10. Update the file res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">LocationDistanceTimeMapV2</string> <string name="action_settings">Settings</string> <string name="hello_world">Tap two locations in the map</string> <string name="str_tv_distance_time">Distance</string> </resources>
11. Update the layout file res/layout/activity_main.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=".MainActivity" > <TextView android:id="@+id/tv_distance_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:text="@string/hello_world" /> <fragment android:id="@+id/map" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_distance_time" /> </RelativeLayout>
12. Create a new class namely “DirectionsJSONParser” in the file “com/sa/googlemap/DirectionsJSONParser.java“
package com.sa.googlemap; 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 DirectionsJSONParser { /** Receives a JSONObject and returns a list of lists containing latitude and longitude */ 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; JSONObject jDistance = null; JSONObject jDuration = 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++) { /** Getting distance from the json data */ jDistance = ((JSONObject) jLegs.get(j)).getJSONObject("distance"); HashMap<String, String> hmDistance = new HashMap<String, String>(); hmDistance.put("distance", jDistance.getString("text")); /** Getting duration from the json data */ jDuration = ((JSONObject) jLegs.get(j)).getJSONObject("duration"); HashMap<String, String> hmDuration = new HashMap<String, String>(); hmDuration.put("duration", jDuration.getString("text")); /** Adding distance object to the path */ path.add(hmDistance); /** Adding duration object to the path */ path.add(hmDuration); 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 = this.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((list.get(l)).latitude)); hm.put("lng", Double.toString((list.get(l)).longitude)); path.add(hm); } } } routes.add(path); } } catch (JSONException e) { e.printStackTrace(); } catch (Exception e) { } return routes; } /** * Method to decode polyline points * 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(((lat / 1E5)), ((lng / 1E5))); poly.add(p); } return poly; } }
13. Update the class “MainActivity” in the file “com/sa/googlemap/MainActivity.java“
package com.sa.googlemap; 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 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 android.view.Menu; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap.OnMapClickListener; import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.model.BitmapDescriptorFactory; 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 MainActivity extends FragmentActivity { GoogleMap map; ArrayList<LatLng> markerPoints; TextView tvDistanceDuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.activity_main); this.tvDistanceDuration = (TextView) this.findViewById(R.id.tv_distance_time); // Initializing this.markerPoints = new ArrayList<LatLng>(); // Getting reference to SupportMapFragment of the activity_main SupportMapFragment fm = (SupportMapFragment) this.getSupportFragmentManager().findFragmentById(R.id.map); // Getting Map for the SupportMapFragment this.map = fm.getMap(); // Enable MyLocation Button in the Map this.map.setMyLocationEnabled(true); // Setting onclick event listener for the map this.map.setOnMapClickListener(new OnMapClickListener() { @Override public void onMapClick(LatLng point) { // Already two locations if (MainActivity.this.markerPoints.size() > 1) { MainActivity.this.markerPoints.clear(); MainActivity.this.map.clear(); } // Adding new item to the ArrayList MainActivity.this.markerPoints.add(point); // Creating MarkerOptions MarkerOptions options = new MarkerOptions(); // Setting the position of the marker options.position(point); /** * For the start location, the color of marker is GREEN and * for the end location, the color of marker is RED. */ if (MainActivity.this.markerPoints.size() == 1) { options.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)); } else if (MainActivity.this.markerPoints.size() == 2) { options.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)); } // Add new marker to the Google Map Android API V2 MainActivity.this.map.addMarker(options); // Checks, whether start and end locations are captured if (MainActivity.this.markerPoints.size() >= 2) { LatLng origin = MainActivity.this.markerPoints.get(0); LatLng dest = MainActivity.this.markerPoints.get(1); // Getting URL to the Google Directions API String url = MainActivity.this.getDirectionsUrl(origin, dest); DownloadTask downloadTask = new DownloadTask(); // Start downloading json data from Google Directions API downloadTask.execute(url); } } }); } private String getDirectionsUrl(LatLng origin, LatLng dest) { // Origin of route String str_origin = "origin=" + origin.latitude + "," + origin.longitude; // Destination of route String str_dest = "destination=" + dest.latitude + "," + dest.longitude; // Sensor enabled String sensor = "sensor=false"; // Building the parameters to the web service String parameters = str_origin + "&" + str_dest + "&" + sensor; // Output format String output = "json"; // Building the url to the web service String url = "https://maps.googleapis.com/maps/api/directions/" + output + "?" + parameters; return url; } /** A method to download json data from url */ private String downloadUrl(String strUrl) throws IOException { String data = ""; InputStream iStream = null; HttpURLConnection urlConnection = null; try { URL url = new URL(strUrl); // Creating an http connection to communicate with url urlConnection = (HttpURLConnection) url.openConnection(); // Connecting to url urlConnection.connect(); // Reading data from url 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 downloading url", e.toString()); } finally { iStream.close(); urlConnection.disconnect(); } return data; } // Fetches data from url passed private class DownloadTask extends AsyncTask<String, Void, String> { // Downloading data in non-ui thread @Override protected String doInBackground(String... url) { // For storing data from web service String data = ""; try { // Fetching the data from web service data = MainActivity.this.downloadUrl(url[0]); } catch (Exception e) { Log.d("Background Task", e.toString()); } return data; } // Executes in UI thread, after the execution of // doInBackground() @Override protected void onPostExecute(String result) { super.onPostExecute(result); ParserTask parserTask = new ParserTask(); // Invokes the thread for parsing the JSON data parserTask.execute(result); } } /** A class to parse the Google Places in JSON format */ private class ParserTask extends AsyncTask<String, Integer, List<List<HashMap<String, String>>>> { // Parsing the data in non-ui thread @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]); DirectionsJSONParser parser = new DirectionsJSONParser(); // Starts parsing data routes = parser.parse(jObject); } catch (Exception e) { e.printStackTrace(); } return routes; } // Executes in UI thread, after the parsing process @Override protected void onPostExecute(List<List<HashMap<String, String>>> result) { ArrayList<LatLng> points = null; PolylineOptions lineOptions = null; MarkerOptions markerOptions = new MarkerOptions(); String distance = ""; String duration = ""; if (result.size() < 1) { Toast.makeText(MainActivity.this.getBaseContext(), "No Points", Toast.LENGTH_SHORT).show(); return; } // Traversing through all the routes for (int i = 0; i < result.size(); i++) { points = new ArrayList<LatLng>(); lineOptions = new PolylineOptions(); // Fetching i-th route List<HashMap<String, String>> path = result.get(i); // Fetching all the points in i-th route for (int j = 0; j < path.size(); j++) { HashMap<String, String> point = path.get(j); if (j == 0) { // Get distance from the list distance = point.get("distance"); continue; } else if (j == 1) { // Get duration from the list duration = point.get("duration"); continue; } double lat = Double.parseDouble(point.get("lat")); double lng = Double.parseDouble(point.get("lng")); LatLng position = new LatLng(lat, lng); points.add(position); } // Adding all the points in the route to LineOptions lineOptions.addAll(points); lineOptions.width(2); lineOptions.color(Color.RED); } MainActivity.this.tvDistanceDuration.setText("Distance:" + distance + ", Duration:" + duration); // Drawing polyline in the Google Map for the i-th route MainActivity.this.map.addPolyline(lineOptions); } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. this.getMenuInflater().inflate(R.menu.main, menu); return true; } }
14. Update the file AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.sa.googlemap" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <uses-permission android:name="android.permission.INTERNET" /> <permission android:name="in.wptrafficanalyzer.locationroutedirectionmapv2.permission.MAPS_RECEIVE" android:protectionLevel="signature" /> <uses-permission android:name="com.sa.googlemap.permission.MAPS_RECEIVE" /> <uses-permission android:name="android.permission.INTERNET" /> <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.sa.googlemap.MainActivity" android:label="@string/app_name" > <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="YOUR_ANDROID_API_KEY" /> </application> </manifest>
Replace “YOUR_ANDROID_API_KEY” with the Google API key obtained in Step 8.
15. Screenshot of the application in execution
Figure 7 : Showing Driving distance and time between two locations in Google Map Android API V2