Android Google Map Draw Path Between Current Location And Destination in this tutorial.
At the end of this tutorial, you will learn how to draw path as i move starting from my current location using google maps.
First of all, we will fetch the current location in terms of latitude and longitude of the user.
Then using this current location and destination coordinates, we will draw a polyline between these two points on android google map.
Following is the output of this example.
Get your Google API
In order to load google map in android, we need to generate one API key in google developer console.
For this purpose, read “Work At Google Developer Console” part in Google Map Android Tutorial. carefully and follow all it’s steps.
Once you have done above step, you will have one Google API key, which we will add in android studio project in few minutes.
After creating API key, we need to enable the Directions API.
Now, watch the following image
At google developer console You can see a navigation icon at upper right corner as per above image. Click on it.
A slider menu will be opened, like the following picture
As per the image, click on APIs option.
System will open the screen like below picture
When you click on Directions API, system will open the following window
Click on the ENABLE button as per above image.
Make sure that you have enabled Direction API otherwise you will not be able to draw a path between two markers.
Now, we have Google API key and we have enable Directions API.
Work At Android Studio
In the android studio, make a fresh new project.
While creating new project, select “Empty Activity” as a default template.
Do not choose Maps Activity at all.
Now go through all the below steps.
Step 1. Adding API key
Time to write Google API key which you have already generated.
Inside res->values->strings.xml file, add the following line
<string name="google_maps_key">YOUR_GOOGLE_API_KEY</string>
Replace “YOUR_GOOGLE_API_KEY” with your original API which you have already generated.
Step 2. Gradle dependency and Manifest changes
We need to add some dependencies that will help us to reduce complexity.
In the build.gradle(Module: app) file, write down the below code lines
implementation 'com.google.android.gms:play-services-maps:16.0.0' implementation 'com.google.android.gms:play-services-location:16.0.0' implementation 'com.karumi:dexter:5.0.0'
- First line will enable us to draw a google map on our android screen.
- Second line will give us access to the required classes which will help us to fetch the current location latitude and longitude of the user.
- Third will allow us to use the dexter library which will simplify the process of runtime permissions in android.
Now in AndroidManifest.xml file, add the below internet and location permissions.
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
We also have to add following code inside <application> tag.
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" />
Final and full code for AndroidManifest.xml file is as the following
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.parsaniahardik.google_map_currentlocationroute"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> <meta-data android:name="com.google.android.geo.API_KEY" android:value="@string/google_maps_key" /> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Step 3. An interface
We need one interface in this example. So make a new class and give it a name TaskLoadedCallback.java
Source code for TaskLoadedCallback.java is as the below
public interface TaskLoadedCallback { void onTaskDone(Object... values); }
Step 4. Making PointParser class
Prepare a new class and named it like PointParser.java
PointParser.java class should contain the following code
import android.content.Context; import android.graphics.Color; import android.os.AsyncTask; import android.util.Log; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.PolylineOptions; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class PointsParser extends AsyncTask<String, Integer, List<List<HashMap<String, String>>>> { TaskLoadedCallback taskCallback; String directionMode = "driving"; public PointsParser(Context mContext, String directionMode) { this.taskCallback = (TaskLoadedCallback) mContext; this.directionMode = directionMode; } // 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]); Log.d("mylog", jsonData[0].toString()); DataParser parser = new DataParser(); Log.d("mylog", parser.toString()); // Starts parsing data routes = parser.parse(jObject); Log.d("mylog", "Executing routes"); Log.d("mylog", routes.toString()); } catch (Exception e) { Log.d("mylog", e.toString()); 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; PolylineOptions lineOptions = null; // Traversing through all the routes for (int i = 0; i < result.size(); i++) { points = new ArrayList<>(); 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); 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); if (directionMode.equalsIgnoreCase("walking")) { lineOptions.width(10); lineOptions.color(Color.MAGENTA); } else { lineOptions.width(20); lineOptions.color(Color.RED); } Log.d("mylog", "onPostExecute lineoptions decoded"); } // Drawing polyline in the Google Map for the i-th route if (lineOptions != null) { //mMap.addPolyline(lineOptions); taskCallback.onTaskDone(lineOptions); } else { Log.d("mylog", "without Polylines drawn"); } } }
- Google will send us one JSON data, which will be parsed using this class.
- When we want to draw the route, we need to parse one URL which consist the location (latitude and longitude) of both origin and destination places.
This URL has the below structure
https://maps.googleapis.com/maps/api/directions/json?origin=Disneyland&destination=Universal+Studios+Hollywood&key=YOUR_API_KEY
Color of Path
I am drawing a red color path in this example.
If you want to change the color of this path, you need to update below lines
if (directionMode.equalsIgnoreCase("walking")) { lineOptions.width(10); lineOptions.color(Color.MAGENTA); } else { lineOptions.width(20); lineOptions.color(Color.RED); }
onPostExecute() method contains the above lines.
Step 5. FetchURL source code
Create a new JAVA class with name FetchURL.java
Source Code block for FetchURL.java is looking like the below
import android.content.Context; import android.os.AsyncTask; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class FetchURL extends AsyncTask<String, Void, String> { Context mContext; String directionMode = "driving"; public FetchURL(Context mContext) { this.mContext = mContext; } @Override protected String doInBackground(String... strings) { // For storing data from web service String data = ""; directionMode = strings[1]; try { // Fetching the data from web service data = downloadUrl(strings[0]); Log.d("mylog", "Background task data " + data.toString()); } catch (Exception e) { Log.d("Background Task", e.toString()); } return data; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); PointsParser parserTask = new PointsParser(mContext, directionMode); // Invokes the thread for parsing the JSON data parserTask.execute(s); } 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(); Log.d("mylog", "Downloaded URL: " + data.toString()); br.close(); } catch (Exception e) { Log.d("mylog", "Exception downloading URL: " + e.toString()); } finally { iStream.close(); urlConnection.disconnect(); } return data; } }
- We will make the http call using the above class.
- This class also holds the mode of transporat.
- Right now the mode is “driving.“
- Possible values for mode is driving, bicycling, transit and walking.
To update the mode, update the below line
String directionMode = "driving";
Step 6. Class of DataParser
Again make a new class and give it a name like DataParser.java
Following is the code structure for DataParser.java
import com.google.android.gms.maps.model.LatLng; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class DataParser { public List<List<HashMap<String, String>>> parse(JSONObject jObject) { List<List<HashMap<String, String>>> routes = new ArrayList<>(); JSONArray jRoutes; JSONArray jLegs; JSONArray jSteps; try { jRoutes = jObject.getJSONArray("routes"); /** Traversing all routes */ for (int i = 0; i < jRoutes.length(); i++) { jLegs = ((JSONObject) jRoutes.get(i)).getJSONArray("legs"); List path = new ArrayList<>(); /** 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<>(); 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 : https://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<>(); 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; } }
Step 7. Main Modifications
Now final thing is to update activity_main.xml and MainActivity.java files.
In the activity_main.xml file, add the below lines
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/tv" android:textSize="18sp" android:textColor="#000"/> <fragment android:id="@+id/mapNearBy" android:name="com.google.android.gms.maps.MapFragment" android:layout_width="match_parent" android:layout_height="0dp" android:layout_below="@+id/rvToolbar" android:layout_weight="1" /> <Button android:id="@+id/btnGetDirection" android:text="Get Direction" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/selectableItemBackground" /> </LinearLayout>
- Three widgets are there in this main layout file.
- First is text view which will hold the value of the current latitude and longitude.
- fragment will allow us to load google map inside itself.
- When the user will click the button, we will draw the line between current location and destination on google map.
Now source code for MainActivity.java is as the below
import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.location.Location; import android.net.Uri; import android.provider.Settings; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapFragment; import com.google.android.gms.maps.OnMapReadyCallback; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.Polyline; import com.google.android.gms.maps.model.PolylineOptions; import com.karumi.dexter.Dexter; import com.karumi.dexter.MultiplePermissionsReport; import com.karumi.dexter.PermissionToken; import com.karumi.dexter.listener.DexterError; import com.karumi.dexter.listener.PermissionRequest; import com.karumi.dexter.listener.PermissionRequestErrorListener; import com.karumi.dexter.listener.multi.MultiplePermissionsListener; import java.util.List; public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, com.google.android.gms.location.LocationListener , OnMapReadyCallback, TaskLoadedCallback{ //variables for map and route private GoogleMap mMap; private MarkerOptions place1, place2; Button getDirection; private Polyline currentPolyline; private MapFragment mapFragment; private boolean isFirstTime = true; //variables for current location private static final String TAG = "MainActivity"; private TextView tvLocation; private GoogleApiClient mGoogleApiClient; private Location mLocation; private LocationRequest mLocationRequest; private com.google.android.gms.location.LocationListener listener; private long UPDATE_INTERVAL = 2 * 1000; /* 10 secs */ private long FASTEST_INTERVAL = 2000; /* 2 sec */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //code for getting current location requestMultiplePermissions(); tvLocation = (TextView) findViewById((R.id.tv)); mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); } //code for drawing route @Override public void onMapReady(GoogleMap googleMap) { mMap = googleMap; mMap.clear(); Log.d("mylog", "Added Markers"); mMap.addMarker(place1); mMap.addMarker(place2); CameraPosition googlePlex = CameraPosition.builder() .target(new LatLng(22.7739,71.6673)) .zoom(7) .bearing(0) .tilt(45) .build(); mMap.animateCamera(CameraUpdateFactory.newCameraPosition(googlePlex), 5000, null); } private String getUrl(LatLng origin, LatLng dest, String directionMode) { // Origin of route String str_origin = "origin=" + origin.latitude + "," + origin.longitude; // Destination of route String str_dest = "destination=" + dest.latitude + "," + dest.longitude; // Mode String mode = "mode=" + directionMode; // Building the parameters to the web service String parameters = str_origin + "&" + str_dest + "&" + mode; // Output format String output = "json"; // Building the url to the web service String url = "https://maps.googleapis.com/maps/api/directions/" + output + "?" + parameters + "&key=" + getString(R.string.google_maps_key); return url; } @Override public void onTaskDone(Object... values) { if (currentPolyline != null) currentPolyline.remove(); currentPolyline = mMap.addPolyline((PolylineOptions) values[0]); } //runtime permission method private void requestMultiplePermissions(){ Dexter.withActivity(this) .withPermissions( Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION ) .withListener(new MultiplePermissionsListener() { @Override public void onPermissionsChecked(MultiplePermissionsReport report) { // check if all permissions are granted if (report.areAllPermissionsGranted()) { Toast.makeText(getApplicationContext(), "All permissions are granted by user!", Toast.LENGTH_SHORT).show(); } // check for permanent denial of any permission if (report.isAnyPermissionPermanentlyDenied()) { // show alert dialog navigating to Settings openSettingsDialog(); } } @Override public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) { token.continuePermissionRequest(); } }). withErrorListener(new PermissionRequestErrorListener() { @Override public void onError(DexterError error) { Toast.makeText(getApplicationContext(), "Some Error! ", Toast.LENGTH_SHORT).show(); } }) .onSameThread() .check(); } private void openSettingsDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setTitle("Required Permissions"); builder.setMessage("This app require permission to use awesome feature. Grant them in app settings."); builder.setPositiveButton("Take Me To SETTINGS", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, 101); } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); builder.show(); } //methods for getting current location @Override public void onConnected(Bundle bundle) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } startLocationUpdates(); mLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); if(mLocation == null){ startLocationUpdates(); } if (mLocation != null) { // mLatitudeTextView.setText(String.valueOf(mLocation.getLatitude())); //mLongitudeTextView.setText(String.valueOf(mLocation.getLongitude())); } else { Toast.makeText(this, "Location not Detected", Toast.LENGTH_SHORT).show(); } } @Override public void onConnectionSuspended(int i) { Log.i(TAG, "Connection Suspended"); mGoogleApiClient.connect(); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { Log.i(TAG, "Connection failed. Error: " + connectionResult.getErrorCode()); } @Override protected void onStart() { super.onStart(); if (mGoogleApiClient != null) { mGoogleApiClient.connect(); } } @Override protected void onStop() { super.onStop(); if (mGoogleApiClient.isConnected()) { mGoogleApiClient.disconnect(); } } protected void startLocationUpdates() { // Create the location request mLocationRequest = LocationRequest.create() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setInterval(UPDATE_INTERVAL) .setFastestInterval(FASTEST_INTERVAL); // Request location updates if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); Log.d("reque", "--->>>>"); } @Override public void onLocationChanged(Location location) { String msg = "Updated Location: " + Double.toString(location.getLatitude()) + "," + Double.toString(location.getLongitude()); tvLocation.setText(String.valueOf(location.getLatitude() +" "+String.valueOf(location.getLongitude()))); Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); if(isFirstTime){ //code to draw path on map getDirection = findViewById(R.id.btnGetDirection); getDirection.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new FetchURL(MainActivity.this).execute(getUrl(place1.getPosition(), place2.getPosition(), "driving"), "driving"); } }); place1 = new MarkerOptions().position(new LatLng(location.getLatitude(), location.getLongitude())).title("Location 1"); place2 = new MarkerOptions().position(new LatLng(20.8880, 70.4012)).title("Location 2"); mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.mapNearBy); mapFragment.getMapAsync(this); isFirstTime = false; } } }
Let us dive deep in the above source code.
- First of all, compiler will create some variables and objects of various classes like GoogleMap, MarkerOptions, Polyline, Location, LocationRequest etc.
- Some of them are for google map and route and some will help to fetch current location.
Now read the onCreate() method.
- Here, compiler will first call the requestMultiplePermissions() method.
- This method will ask for runtime permissions.
- Then compiler will initialize textview and mGoogleApiClient.
- Compiler will call onMapReady() function when map has finished it’s loading.
- getUrl() and onTaskDone() methods are the part of drawing the route process.
- onConnected(), onConnectionSuspended(), onConnectionFailed(), onStart(), onStop() and startLocationUpdates() methods are the part of getting the current location.
Now read the onLocationChanged() method.
- In this method, we will get current latitude and longitude.
- Compiler will set the value of latitude and longitude in text view and then it will show one toast.
- After that, it will check one if condition. Compiler will go inside this if condition only once.
- Inside if condition, compiler will set the code for button’s click event.
Following line will draw the path.
new FetchURL(MainActivity.this).execute(getUrl(place1.getPosition(), place2.getPosition(), "driving"), "driving");
Then compiler will execute the map loading process.
How to draw path when user is moving
Here, we are drawing the path on the user click only.
If you want to draw path with the movement of the user then simply write below line inside onLocationChanged() method instead of writing it inside button click.
Source code for this may be looking like
@Override public void onLocationChanged(Location location) { place1 = new MarkerOptions().position(new LatLng(location.getLatitude(), location.getLongitude())).title("Location 1"); place2 = new MarkerOptions().position(new LatLng(20.8880, 70.4012)).title("Location 2"); mapFragment = (MapFragment) getFragmentManager().findFragmentById(R.id.mapNearBy); mapFragment.getMapAsync(this); new FetchURL(MainActivity.this).execute(getUrl(place1.getPosition(), place2.getPosition(), "driving"), "driving"); }
Above code will constantly draw the line as per the movement of the user.
May be you get some errors
There are possibility for two types of errors in this example.
First is something like “This API project is not authorized to use this API“.
See the following image
Search for “mylog” in logcat as per above picture.
I was getting this error and it took me 6 hours to solve this. And the problem was that my Directions API was not enable !!
So do not waste your time when you are getting this error and quickly enable Directions API. (This is the prime intention of this blog to help you solve this type of simple errors that might eat your big amount of time)
Second possible error is that “You have exceeded your daily request” like following picture
This error is saying that your free quota for today is over. So you need to wait for 24 hours or you can create a new API to go further.