Open In App

How to Generate Route Between Two Locations in Google Map in Android?

Last Updated : 18 Jul, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

Google Map or any other such applications have methods to generate a route between two locations. Generally, there are a lot of parameters like closest distance, the fastest distance, alternative routes, etc to suffice the needs. These apps are really appealing, but the developer knows the pain behind developing such beautiful applications. 

Through this article, we will show you how you can generate a route between two locations in a Google Map in Android. Follow the below steps to begin.

Step by Step Implementation

Step 1: Create a New Project in Android Studio

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio. We demonstrated the application in Kotlin, so make sure you select Kotlin as the primary language while creating a New Project.

Step 2: Add these dependencies and sync the project

// For Map fragment

implementation ‘com.google.android.libraries.places:places:2.4.0’

// To make a call to for getting Coordinates response from a Web URL

implementation ‘com.squareup.okhttp3:okhttp:4.9.0’

Step 3: Add this permission in AndroidManifest.xml file

<manifest…..>

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

   <application…….>

     </application…….>

 </manifest…..>

Step 4: Add this Google Map fragment in the activity_main.xml file

XML




<!--Give it an ID as we will call this in the Main code-->
<fragment
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:id="@+id/map"
  tools:context=".MapsActivity"
  android:name="com.google.android.gms.maps.SupportMapFragment"/>


Step 5: Get and store your Places API Key

  1. Our application utilizes Google’s Places API, so we need to get the Places API key from Google. To get an API key, please refer to Generating API Keys For Using Any Google APIs.
  2. Hiding an API key is essential and to do so, please refer to How to Hide API and Secret Keys in Android Studio?

Step 6: Retrieve & validate your key in MainActivity

After referring to the above two articles, you will have your API key called in the Main. To validate the key for using Map Fragment, add this code.

Kotlin




if (!Places.isInitialized()) {
            Places.initialize(applicationContext, apiKey)
        }


Step 7: Initialize the Map Fragment

After appending the below code, the IDE will want to extend the MainActivity to OnMapReadyCallback. Apply these changes. You will also need to implement the member functions now. The member function will be onMapReady. Once done, the code will have no errors.

Kotlin




val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)


Step 8: Get the coordinates of two places

We have manually declared the latitude and longitude values of two places, between whom we wish to generate a route. We declared them globally. But you can use your own methods to get those coordinates.

Kotlin




// GeeksforGeeks coordinates
private var originLatitude: Double = 28.5021359
private var originLongitude: Double = 77.4054901
  
// Coordinates of a park nearby
private var destinationLatitude: Double = 28.5151087
private var destinationLongitude: Double = 77.3932163


Step 9: Edit the onMapReady function

mMap is already declared in the global variable.

Kotlin




private lateinit var mMap: GoogleMap


Now we shall change the onMapReady call.

Kotlin




override fun onMapReady(p0: GoogleMap?) {
        mMap = p0!!
        val originLocation = LatLng(originLatitude, originLongitude)
        mMap.clear()
        mMap.addMarker(MarkerOptions().position(originLocation))
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(originLocation, 18F))
    }


Step 10: Create a function to generate the direction URL

Kotlin




private fun getDirectionURL(origin:LatLng, dest:LatLng, secret: String) : String{
       return "https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}" +
               "&destination=${dest.latitude},${dest.longitude}" +
               "&sensor=false" +
               "&mode=driving" +
               "&key=$secret"
   }


Step 11: Create a function to decode polyline

Kotlin




fun decodePolyline(encoded: String): List<LatLng> {
        val poly = ArrayList<LatLng>()
        var index = 0
        val len = encoded.length
        var lat = 0
        var lng = 0
        while (index < len) {
            var b: Int
            var shift = 0
            var result = 0
            do {
                b = encoded[index++].code - 63
                result = result or (b and 0x1f shl shift)
                shift += 5
            } while (b >= 0x20)
            val dlat = if (result and 1 != 0) (result shr 1).inv() else result shr 1
            lat += dlat
            shift = 0
            result = 0
            do {
                b = encoded[index++].code - 63
                result = result or (b and 0x1f shl shift)
                shift += 5
            } while (b >= 0x20)
            val dlng = if (result and 1 != 0) (result shr 1).inv() else result shr 1
            lng += dlng
            val latLng = LatLng((lat.toDouble() / 1E5),(lng.toDouble() / 1E5))
            poly.add(latLng)
        }
        return poly
    }


Step 12: Create a class for Map Parameters

The response when Step 10 is passed requires an instance of the object to catch the JSON. This class is that object.

Kotlin




class MapData {
    var routes = ArrayList<Routes>()
}
  
class Routes {
    var legs = ArrayList<Legs>()
}
  
class Legs {
    var distance = Distance()
    var duration = Duration()
    var end_address = ""
    var start_address = ""
    var end_location =Location()
    var start_location = Location()
    var steps = ArrayList<Steps>()
}
  
class Steps {
    var distance = Distance()
    var duration = Duration()
    var end_address = ""
    var start_address = ""
    var end_location =Location()
    var start_location = Location()
    var polyline = PolyLine()
    var travel_mode = ""
    var maneuver = ""
}
  
class Duration {
    var text = ""
    var value = 0
}
  
class Distance {
    var text = ""
    var value = 0
}
  
class PolyLine {
    var points = ""
}
  
class Location{
    var lat =""
    var lng =""
}


Step 13: Create an inner class to pass the URL string generated in Step 8 and call the decode polyline function

Kotlin




@SuppressLint("StaticFieldLeak")
    private inner class GetDirection(val url : String) : AsyncTask<Void, Void, List<List<LatLng>>>(){
        override fun doInBackground(vararg params: Void?): List<List<LatLng>> {
            
            val client = OkHttpClient()
            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
            val data = response.body!!.string()
  
            val result =  ArrayList<List<LatLng>>()
            try{
                val respObj = Gson().fromJson(data,MapData::class.java)
                val path =  ArrayList<LatLng>()
                for (i in 0 until respObj.routes[0].legs[0].steps.size){
                    path.addAll(decodePolyline(respObj.routes[0].legs[0].steps[i].polyline.points))
                }
                result.add(path)
            }catch (e:Exception){
                e.printStackTrace()
            }
            return result
        }
  
        override fun onPostExecute(result: List<List<LatLng>>) {
            val lineoption = PolylineOptions()
            for (i in result.indices){
                lineoption.addAll(result[i])
                lineoption.width(10f)
                lineoption.color(Color.GREEN)
                lineoption.geodesic(true)
            }
            mMap.addPolyline(lineoption)
      }
}


Complete Source Codes:

Download the Source Code from here.

Kotlin




// MainActivity.kt
  
import android.annotation.SuppressLint
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Color
import android.os.AsyncTask
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
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.LatLng
import com.google.android.gms.maps.model.MarkerOptions
import com.google.android.gms.maps.model.PolylineOptions
import com.google.android.libraries.places.api.Places
import com.google.gson.Gson
import okhttp3.OkHttpClient
import okhttp3.Request
  
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
  
    private lateinit var mMap: GoogleMap
    private var originLatitude: Double = 28.5021359
    private var originLongitude: Double = 77.4054901
    private var destinationLatitude: Double = 28.5151087
    private var destinationLongitude: Double = 77.3932163
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        // Fetching API_KEY which we wrapped
        val ai: ApplicationInfo = applicationContext.packageManager
            .getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
        val value = ai.metaData["com.google.android.geo.API_KEY"]
        val apiKey = value.toString()
         
          // Initializing the Places API with the help of our API_KEY
        if (!Places.isInitialized()) {
            Places.initialize(applicationContext, apiKey)
        }
          
        // Map Fragment
        val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
  
        val gd = findViewById<Button>(R.id.directions)
        gd.setOnClickListener{
            mapFragment.getMapAsync {
                mMap = it
                val originLocation = LatLng(originLatitude, originLongitude)
                mMap.addMarker(MarkerOptions().position(originLocation))
                val destinationLocation = LatLng(destinationLatitude, destinationLongitude)
                mMap.addMarker(MarkerOptions().position(destinationLocation))
                val urll = getDirectionURL(originLocation, destinationLocation, apiKey)
                GetDirection(urll).execute()
                mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(originLocation, 14F))
            }
        }
    }
  
    override fun onMapReady(p0: GoogleMap?) {
        mMap = p0!!
        val originLocation = LatLng(originLatitude, originLongitude)
        mMap.clear()
        mMap.addMarker(MarkerOptions().position(originLocation))
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(originLocation, 18F))
    }
  
    private fun getDirectionURL(origin:LatLng, dest:LatLng, secret: String) : String{
        return "https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}" +
                "&destination=${dest.latitude},${dest.longitude}" +
                "&sensor=false" +
                "&mode=driving" +
                "&key=$secret"
    }
  
    @SuppressLint("StaticFieldLeak")
    private inner class GetDirection(val url : String) : AsyncTask<Void, Void, List<List<LatLng>>>(){
        override fun doInBackground(vararg params: Void?): List<List<LatLng>> {
            val client = OkHttpClient()
            val request = Request.Builder().url(url).build()
            val response = client.newCall(request).execute()
            val data = response.body!!.string()
  
            val result =  ArrayList<List<LatLng>>()
            try{
                val respObj = Gson().fromJson(data,MapData::class.java)
                val path =  ArrayList<LatLng>()
                for (i in 0 until respObj.routes[0].legs[0].steps.size){
                    path.addAll(decodePolyline(respObj.routes[0].legs[0].steps[i].polyline.points))
                }
                result.add(path)
            }catch (e:Exception){
                e.printStackTrace()
            }
            return result
        }
  
        override fun onPostExecute(result: List<List<LatLng>>) {
            val lineoption = PolylineOptions()
            for (i in result.indices){
                lineoption.addAll(result[i])
                lineoption.width(10f)
                lineoption.color(Color.GREEN)
                lineoption.geodesic(true)
            }
            mMap.addPolyline(lineoption)
        }
    }
  
    fun decodePolyline(encoded: String): List<LatLng> {
        val poly = ArrayList<LatLng>()
        var index = 0
        val len = encoded.length
        var lat = 0
        var lng = 0
        while (index < len) {
            var b: Int
            var shift = 0
            var result = 0
            do {
                b = encoded[index++].code - 63
                result = result or (b and 0x1f shl shift)
                shift += 5
            } while (b >= 0x20)
            val dlat = if (result and 1 != 0) (result shr 1).inv() else result shr 1
            lat += dlat
            shift = 0
            result = 0
            do {
                b = encoded[index++].code - 63
                result = result or (b and 0x1f shl shift)
                shift += 5
            } while (b >= 0x20)
            val dlng = if (result and 1 != 0) (result shr 1).inv() else result shr 1
            lng += dlng
            val latLng = LatLng((lat.toDouble() / 1E5),(lng.toDouble() / 1E5))
            poly.add(latLng)
        }
        return poly
    }
}


Kotlin




// MapData.kt
  
class MapData {
    var routes = ArrayList<Routes>()
}
  
class Routes {
    var legs = ArrayList<Legs>()
}
  
class Legs {
    var distance = Distance()
    var duration = Duration()
    var end_address = ""
    var start_address = ""
    var end_location =Location()
    var start_location = Location()
    var steps = ArrayList<Steps>()
}
  
class Steps {
    var distance = Distance()
    var duration = Duration()
    var end_address = ""
    var start_address = ""
    var end_location =Location()
    var start_location = Location()
    var polyline = PolyLine()
    var travel_mode = ""
    var maneuver = ""
}
  
class Duration {
    var text = ""
    var value = 0
}
  
class Distance {
    var text = ""
    var value = 0
}
  
class PolyLine {
    var points = ""
}
  
class Location{
    var lat =""
    var lng =""
}


XML




<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  
    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/map"
        tools:context=".MapsActivity"
        android:name="com.google.android.gms.maps.SupportMapFragment"/>
  
    <Button
        android:id="@+id/directions"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:text = "Click"
        tools:ignore="MissingConstraints" />
  
</RelativeLayout>


Output:



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads