Android Google Map Trace Route Between Two Locations

By | March 1, 2016

In the previous series of tutorial we have seen how to integrate google maps in android application and also we have seen how to show current location on google maps , this tutorial is the extension of previous two tutorials here we are going to learn about how to draw route between two markers(lat and long) ie between source and destination .

In this tutorial we will set the current location as source point and destination point will be set dynamically for the marker which is being clicked and then we will draw a polyline points between source and destination .

android-google-map-trace-route-between-two-locations

 

We need API Key to access the Google service for displaying the maps. In the previous tutorial on  Android Google Map  i have explained how to get key .

Build Gradle

Open the gradle file , add below piece of code in dependencies and  sync.

file : build.gradle

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.google.android.gms:play-services:8.4.0'
}

Placing Google Map Key

Place Google Map Key in a XML file as show in below file path.

file : src/debug/res/values/google_maps_api.xml.

<resources>
    <string
        name="google_maps_key"
        templateMergeStrategy="preserve"
        translatable="false">YOUR_KEY_HERE
    </string>
</resources>

Android Manifest

In the manifest file add the meta data tag in with a attribute android:value takes the “Google API Key” and then add permissions INTERNET , ACCES_FINE_LOCATION and ACCESS_COARSE_LOCATION .

file : Manifest.xml

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

    <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:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key" />

        <activity
            android:name=".MapsActivity"
            android:label="@string/title_activity_maps">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

Application Class

  • Create a class MyApplication which extends Application ,this application object represents singleton pattern , the purpose of this class is make volley request available through out of the application
  • The getInstance method of application class returns application object.
  • The getReqQueue method returns the RequestQueue object.
  • Inside addToReqQueue method we are adding calling add() method upon RequestObject and passing request as paramter to it .
  • The cancelPendingReq is used for cancelling the request.

 

file : MyApplication.java

package com.tutorialsbuzz.androidgooglemaps;
 
import android.app.Application;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
 
public class MyApplication extends Application {
 
    private RequestQueue mRequestQueue;
    private static MyApplication mInstance;
 
    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
    }
 
    public static synchronized MyApplication getInstance() {
        return mInstance;
    }
 
    public RequestQueue getReqQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }
 
        return mRequestQueue;
    }
 
    public <T> void addToReqQueue(Request<T> req, String tag) {
 
        getReqQueue().add(req);
    }
 
    public <T> void addToReqQueue(Request<T> req) {
 
        getReqQueue().add(req);
    }
 
    public void cancelPendingReq(Object tag) {
        if (mRequestQueue != null) {
            mRequestQueue.cancelAll(tag);
        }
    }
}

Place a json file “city.json” inside the asset folder of your application which contains a json array of cities with name ,lat, long .

file : asset/city.json

{
 "cities":[
  {
   "name":"Delhi",
   "lat":28.38,
   "long":77.12
  },
  {
   "name":"Hyderbad",
   "lat":17.3700,
   "long":78.4800
  },
  {
   "name":"Mumbai",
   "lat":18.9750,
   "long":72.8258
  },
  {
   "name":"Kolkata",
   "lat":22.5667,
   "long":88.3667
  },
  {
   "name":"Chennai",
   "lat":13.0827,
   "long":80.2707
  },
  {
   "name":"Ahmedabad",
   "lat":23.0300,
   "long":72.5800
  }
 ]
}

Bean Class

Create a java bean class Cities

file : Cities.java

package com.tutorialsbuzz.androidgooglemaps;
 
public class Cities  {
 
    String name;
    Double latitude;
    Double longitude;
 
    public Cities(String name,Double latitude,Double longitude){
        this.name=name;
        this.latitude=latitude;
        this.longitude = longitude;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public Double getLatitude() {
        return latitude;
    }
 
    public void setLatitude(Double latitude) {
        this.latitude = latitude;
    }
 
    public Double getLongitude() {
        return longitude;
    }
 
    public void setLongitude(Double longitude) {
        this.longitude = longitude;
    }
}

XML Layout

Create a XML layout with Framelayout as parent containing Fragment and Relative_layout as child to it
>Fragment is used to render the google maps .
>RelativeLayout which appears at the bottom on click of marker icon in map.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:map="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/map"
        android:name="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.tutorialsbuzz.androidgooglemaps.MapsActivity" />
 
    <RelativeLayout
        android:id="@+id/footer_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="#ffffff"
        android:onClick="getDirection"
        android:padding="15dp"
        android:visibility="gone">
 
        <TextView
            android:id="@+id/tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="22sp"
            android:textStyle="bold"/>
 
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/tv"
            android:onClick="getDirection"
            android:text="Trace" />
 
    </RelativeLayout>
 
</FrameLayout>

Map Direction Parser

When we make a http request to map direction api by passing source and destination latitude and longitude we will get complex json object , to parse this json we will create a class MapDirectonParse which decode and list polyline points .

file : MapDirectionParser.java

package com.tutorialsbuzz.androidgooglemaps;

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 MapDirectionsParser {

    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 to decode polyline points
     */
    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;
    }
}

MapActivity

  • Create a Class MapsActivity which extend FragementActvity and then implements onMapReadyCallback , LocationListener and onMarkerClickListener.
  • onMapReady() callback is triggered when the map is ready to be used , Inside this method check provider (i.e GPS Provider) is avialable or not by making a call to isProviderAvaialble() method (this method return true if GPS is avilable else false).
  • If GPS Provider is avaialble then get the current location using LocationManager and then update the location with custom icon and animation by adding circular border and also inside onLocationChanged() method make location update .
  • Inside addMarks method which initialize the list of cities and add markers on the map of the listes cities .
  • onMarkerClick method triggered on click of marker icon in maps , were we will make relative layout(which contains button with label “Trace”) at the bottom
  • For trace button we have set a method “getDirection” to be triggered on click of it as you can see in above xml layout .
  • Inside getDirection method we will make a call to traceMe by passing source and destination lat and long .
  • Inside the traceMe method we will make a google map api request
    String url = “https://maps.googleapis.com/maps/api/directions/json?origin=” + srcParam + “&destination=” + destParam + “&sensor=false&units=metric&mode=driving”;

    The above url is the google map api request , the important request parameter “orgin” and “destination” which takes source and destination lat and long respctively .
    With parse method of MapDirectonParse class we will parse the json response and draw ployline points between source and destination

file : MapsActivity.java

package com.tutorialsbuzz.androidgooglemaps;

import android.Manifest;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.support.v4.app.FragmentActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
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 org.json.JSONArray;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, LocationListener, GoogleMap.OnMarkerClickListener {

    private GoogleMap mMap;
    private LocationManager mLocationManager = null;
    private String provider = null;
    private Marker mCurrentPosition = null;
    ProgressDialog PD;
    private List<Cities> cityList;
    private ArrayList<LatLng> traceOfMe = null;
    private Polyline mPolyline = null;
    private LatLng mSourceLatLng = null;
    private LatLng mDestinationLatLng = null;
    private TextView tv = null;
    private RelativeLayout mFooterLayout = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        tv = (TextView) findViewById(R.id.tv);
        mFooterLayout = (RelativeLayout) findViewById(R.id.footer_layout);
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        mMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);

        addMarks(mMap);

        if (isProviderAvailable() && (provider != null)) {
            locateCurrentPosition();
        }

    }

    private void addMarks(GoogleMap googleMap) {

        initCities();
        mMap = googleMap;

        for (int i = 0; i < cityList.size(); i++) {
            Cities city = cityList.get(i);
            LatLng latLng = new LatLng(city.getLatitude(), city.getLongitude());
            MarkerOptions markerOptions = new MarkerOptions();
            markerOptions.position(latLng);
            markerOptions.title(city.getName());
            mMap.addMarker(markerOptions);
            mMap.setOnMarkerClickListener((GoogleMap.OnMarkerClickListener) this);
        }

        // traceMe();
    }


    public void initCities() {
        cityList = new ArrayList<Cities>();
        String json = loadJSONFromAsset("city.json");

        try {
            JSONObject jsonObject = new JSONObject(json);
            JSONArray jsonArray = jsonObject.getJSONArray("cities");

            for (int i = 0; i < jsonArray.length(); i++) {
                JSONObject city = jsonArray.getJSONObject(i);
                Cities cities = new Cities(city.getString("name"), city.getDouble("lat"), city.getDouble("long"));
                cityList.add(cities);
            }

        } catch (Exception e) {

        }
    }

    public String loadJSONFromAsset(String filename) {
        String json = null;
        try {
            InputStream is = getAssets().open(filename);
            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            json = new String(buffer, "UTF-8");
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
        return json;
    }

    private void locateCurrentPosition() {

        int status = getPackageManager().checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION,
                getPackageName());

        if (status == PackageManager.PERMISSION_GRANTED) {
            Location location = mLocationManager.getLastKnownLocation(provider);
            updateWithNewLocation(location);
            //  mLocationManager.addGpsStatusListener(this);
            long minTime = 5000;// ms
            float minDist = 5.0f;// meter
            mLocationManager.requestLocationUpdates(provider, minTime, minDist,
                    this);
        }
    }


    private boolean isProviderAvailable() {
        mLocationManager = (LocationManager) getSystemService(
                Context.LOCATION_SERVICE);
        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
        criteria.setAltitudeRequired(false);
        criteria.setBearingRequired(false);
        criteria.setCostAllowed(true);
        criteria.setPowerRequirement(Criteria.POWER_LOW);

        provider = mLocationManager.getBestProvider(criteria, true);
        if (mLocationManager
                .isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
            provider = LocationManager.NETWORK_PROVIDER;

            return true;
        }

        if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            provider = LocationManager.GPS_PROVIDER;
            return true;
        }

        if (provider != null) {
            return true;
        }
        return false;
    }

    private void updateWithNewLocation(Location location) {

        if (location != null && provider != null) {
            double lng = location.getLongitude();
            double lat = location.getLatitude();

            mSourceLatLng = new LatLng(lat, lng);

            addBoundaryToCurrentPosition(lat, lng);

            CameraPosition camPosition = new CameraPosition.Builder()
                    .target(new LatLng(lat, lng)).zoom(10f).build();

            if (mMap != null)
                mMap.animateCamera(CameraUpdateFactory
                        .newCameraPosition(camPosition));
        } else {
            Log.d("Location error", "Something went wrong");
        }

    }


    private void addBoundaryToCurrentPosition(double lat, double lang) {

        MarkerOptions mMarkerOptions = new MarkerOptions();
        mMarkerOptions.position(new LatLng(lat, lang));
        mMarkerOptions.icon(BitmapDescriptorFactory
                .fromResource(R.drawable.marker_current));
        mMarkerOptions.anchor(0.5f, 0.5f);

        CircleOptions mOptions = new CircleOptions()
                .center(new LatLng(lat, lang)).radius(10000)
                .strokeColor(0x110000FF).strokeWidth(1).fillColor(0x110000FF);
        mMap.addCircle(mOptions);
        if (mCurrentPosition != null)
            mCurrentPosition.remove();
        mCurrentPosition = mMap.addMarker(mMarkerOptions);
    }


    @Override
    public void onLocationChanged(Location location) {

        updateWithNewLocation(location);
    }

    @Override
    public void onProviderDisabled(String provider) {

        updateWithNewLocation(null);
    }

    @Override
    public void onProviderEnabled(String provider) {

    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        switch (status) {
            case LocationProvider.OUT_OF_SERVICE:
                break;
            case LocationProvider.TEMPORARILY_UNAVAILABLE:
                break;
            case LocationProvider.AVAILABLE:
                break;
        }
    }


    private void traceMe(LatLng srcLatLng, LatLng destLatLng) {

        PD = new ProgressDialog(MapsActivity.this);
        PD.setMessage("Loading..");
        PD.show();

        String srcParam = srcLatLng.latitude + "," + srcLatLng.longitude;
        String destParam = destLatLng.latitude + "," + destLatLng.longitude;

        String url = "https://maps.googleapis.com/maps/api/directions/json?origin=" + srcParam + "&destination=" + destParam + "&sensor=false&units=metric&mode=driving";
        JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.GET, url,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {

                        MapDirectionsParser parser = new MapDirectionsParser();
                        List<List<HashMap<String, String>>> routes = parser.parse(response);
                        ArrayList<LatLng> points = null;

                        for (int i = 0; i < routes.size(); i++) {
                            points = new ArrayList<LatLng>();
                            // lineOptions = new PolylineOptions();

                            // Fetching i-th route
                            List<HashMap<String, String>> path = routes.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);
                            }
                        }

                        drawPoints(points, mMap);
                        PD.dismiss();

                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        PD.dismiss();
                    }
                });

        MyApplication.getInstance().addToReqQueue(jsonObjectRequest);

    }


    private void drawPoints(ArrayList<LatLng> points, GoogleMap mMaps) {
        if (points == null) {
            return;
        }
        traceOfMe = points;
        PolylineOptions polylineOpt = new PolylineOptions();
        for (LatLng latlng : traceOfMe) {
            polylineOpt.add(latlng);
        }
        polylineOpt.color(Color.BLUE);
        if (mPolyline != null) {
            mPolyline.remove();
            mPolyline = null;
        }
        if (mMap != null) {
            mPolyline = mMap.addPolyline(polylineOpt);

        } else {

        }
        if (mPolyline != null)
            mPolyline.setWidth(10);

        mFooterLayout.setVisibility(View.GONE);
    }


    @Override
    public boolean onMarkerClick(Marker marker) {
        mDestinationLatLng = marker.getPosition();
        marker.getTitle();

        mFooterLayout.setVisibility(View.VISIBLE);
        tv.setText("Trace To " + marker.getTitle());

        return false;
    }

    public void getDirection(View view) {
        //    tv.setText("SourceText: " + mSourceLatLng + "n" + "DestinationText" + mDestinationLatLng);

        if (mSourceLatLng != null && mDestinationLatLng != null) {
            traceMe(mSourceLatLng, mDestinationLatLng);
        }
    }
}

androidgooglemapsroute

  • Your thinking toward the respective issue is awesome also the idea behind the blog is very interesting which would bring a new evolution in respective field. Thanks for sharing.

    Android Training in Chennai

  • Thanks. Worked on Marshmallow

  • Shivani

    Need help regarding google maps.
    I am trying to show a directional route between the two marker points.
    But i can see a straight line between the marker points on my map instead of a directional route.
    Please help me solve this issue.
    Awaiting your reply.