Android Google Map Draw Path/Line/Route Between Two Points/Markers

retrofit android get json, android retrofit listview, android recyclerview retrofit, android spinner example to load json using retrofit, android google map draw path, android google map draw path between current location and destination

Learn about Android Google Map Draw Path or Polyline Between two markers tutorial example.

In this tutorial, we will draw a route between two points on google map in our android app.

If you are making an app for cab locator or something like this, you need to show path or line between two points on the google map.

This tutorial will simplify this process for you.

See the below video which is the last output of this example

Creating API on google console

First of all, we need to create a new application on google developer console.

For this, follow the “Work At Google Developer Console” part in Google Map Android Tutorial.

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, see the below image

android google map draw path

 

  • At google developer console Click on the navigation menu icon as show in above image.
  • System will open one sliding menu which is looking like the below image
android google map draw path
Sliding menu
  • As per the image, click on APIs option.

It will led you to the screen like the following image

android google map draw path
Direction API

When you click on Directions API, system will open the below screen

android google map draw path
ENABLE API
  • Click on the ENABLE button as per image.
  • Make sure that you have enabled Direction API otherwise you will not be able to draw a path between two markers.

At this stage, we have Google API key and we have enable Directions API.

Tasks on Android Studio

Now come to android studio and create a new project.

Do not select “Maps Activity”, while making new project instead select “Empty Activity.”

Now follow all the below steps.

Step 1. Writing API key

Under the res->values->strings.xml file add the below code 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. Dependency and Manifest Works

Write down the below line in the build.gradle(Module: app) file.

 implementation 'com.google.android.gms:play-services-maps:16.0.0'
  • Above file is fetching the required classes to use google map in our project.

Now in the AndroidManifest.xml file, add the below code structure in <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" />

Now add the internet permission,

 <uses-permission android:name="android.permission.INTERNET" />

Final code for AndroidManifest.xml file is as the below

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.parsaniahardik.google_map_path_two_location">

    <uses-permission android:name="android.permission.INTERNET" />

    <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. Required Interface

Create a new JAVA class named TaskLoadedCallback.java

Source code for TaskLoadedCallback.java is as the following

public interface TaskLoadedCallback {
    void onTaskDone(Object... values);
}

Step 4. PointParser class

Make a new JAVA class and give it a name like PointParser.java

Add the following source code in PointParser.java

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");
        }
    }
}
  • This class will parse the JSON data which we have received from the google.
  • 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 is looking like below

https://maps.googleapis.com/maps/api/directions/json?origin=Disneyland&destination=Universal+Studios+Hollywood&key=YOUR_API_KEY

This URL is just for information, we do not need to add it to our project.

Changing the color of path

By default, we will draw a RED colored path between two markers.

If you want to change it’s color than you should change below lines

 if (directionMode.equalsIgnoreCase("walking")) {
                lineOptions.width(10);
                lineOptions.color(Color.MAGENTA);
            } else {
                lineOptions.width(20);
                lineOptions.color(Color.RED);
  }

Above code is the part of the onPostExecute() method.

Step 5. FetchURL code

Prepare another JAVA class and name of this class should be 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;
    }
}

This class is making the http call to the URL.

In this class, we are also setting the mode of transport.

We’ve set the directionMode=driving in the current application.
The other modes of transport are:

  • driving (default)
  • walking
  • bicycling
  • transit

To change the mode, update the below line

String directionMode = "driving";

Step 6. DataParser class

Time to create a new class and it’s name is like DataParser.java

Code block of DataParser.java is something like the following

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;
    }
}

As the name suggests, this class is parsing the JSON data which google have sent us.

Step 7. Final Words

Now we just need to change two main files. activity_main.xml and MainActivity.java

Source code for activity_main.xml is something like the following

<?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">

    <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>
  • First widget is the fragment. We will load the google map in this widget only.
  • Second one is the button. When the user will click the button, we will draw a polyline between two points.

Now in the MainActivity.java file, write down the following source code lines

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;

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;

public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, TaskLoadedCallback {

    private GoogleMap mMap;
    private MarkerOptions place1, place2;
    Button getDirection;
    private Polyline currentPolyline;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        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");
            }
        });
        //27.658143,85.3199503
        //27.667491,85.3208583
        place1 = new MarkerOptions().position(new LatLng(22.3039, 70.8022)).title("Location 1");
        place2 = new MarkerOptions().position(new LatLng(23.0225, 72.5714)).title("Location 2");
        MapFragment mapFragment = (MapFragment) getFragmentManager()
                .findFragmentById(R.id.mapNearBy);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        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]);
    }
}

Let us understand above code step by step.

Consider the following code

 private GoogleMap mMap;
    private MarkerOptions place1, place2;
    Button getDirection;
    private Polyline currentPolyline;
  • Using above lines, compiler will create objects of various classes like GoogleMap, MarkerOptions etc.

There are below important lines in the onCreate() method.

  place1 = new MarkerOptions().position(new LatLng(22.3039, 70.8022)).title("Location 1");
  place2 = new MarkerOptions().position(new LatLng(23.0225, 72.5714)).title("Location 2");
  MapFragment mapFragment = (MapFragment) getFragmentManager()
                .findFragmentById(R.id.mapNearBy);
        mapFragment.getMapAsync(this);
  • First two lines are adding two markers on the google map.
  • You can change latitude and longitude of source and destination (place1 and place2) using first two lines.
  • Then compiler will get the mapfragment using the findFragmentById and then it will initialize the google map.

When the google map has finished it’s initialization process, then it will animate the camera using below code

 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);

You can change some camera parameters like zoom, tilt, target place etc.

When the user clicks the button, compiler will execute the below coding lines

 getDirection.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new FetchURL(MainActivity.this).execute(getUrl(place1.getPosition(), place2.getPosition(), "driving"), "driving");
            }
        });

Here, it is the starting point of the process of drawing the path route between markers.

If you have followed above tutorial lines perfectly then you should get the output as you have shown in the output video at the starting of the tutorial.

Possible Errors

When drawing route on google map, there can be two most common errors you may have to face.

First is like This API project is not authorized to use this API.

Look at this below image

Search for “mylog” as per the above image in the logcat.

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 image

It means that your free trial is over. Either you have to use your credit card to get more requests or simply wait for 24 hours.

Download Source Code for Android Google Map Draw Path

Download Source code for Google_Map_Path_Two_Location