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

18 Jul, 2021
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 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.
Chapters
descriptions off, selected
captions settings, opens captions settings dialog
captions off, selected
English
This is a modal window.
Beginning of dialog window. Escape will cancel and close the window.
End of dialog window.
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
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.Hiding an API key is essential and to do so, please refer to How to Hide API and Secret Keys in Android Studio?
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.
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 SupportMapFragmentmapFragment.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 coordinatesprivate var originLatitude: Double = 28.5021359private var originLongitude: Double = 77.4054901  // Coordinates of a park nearbyprivate var destinationLatitude: Double = 28.5151087private 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)      }}
Download the Source Code from here.
Kotlin
Kotlin
XML
// MainActivity.kt  import android.annotation.SuppressLintimport android.content.pm.ApplicationInfoimport android.content.pm.PackageManagerimport android.graphics.Colorimport android.os.AsyncTaskimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.widget.Buttonimport com.google.android.gms.maps.CameraUpdateFactoryimport com.google.android.gms.maps.GoogleMapimport com.google.android.gms.maps.OnMapReadyCallbackimport com.google.android.gms.maps.SupportMapFragmentimport com.google.android.gms.maps.model.LatLngimport com.google.android.gms.maps.model.MarkerOptionsimport com.google.android.gms.maps.model.PolylineOptionsimport com.google.android.libraries.places.api.Placesimport com.google.gson.Gsonimport okhttp3.OkHttpClientimport 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    }}
// 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 version="1.0" encoding="utf-8"?><RelativeLayout     xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    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:
Android
Kotlin
Android
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
How to Add Views Dynamically and Store Data in Arraylist in Android?
Android SDK and it's Components
Flutter - Custom Bottom Navigation Bar
How to Communicate Between Fragments in Android?
Retrofit with Kotlin Coroutine in Android
Android UI Layouts
Kotlin Array
Retrofit with Kotlin Coroutine in Android
Bundle in Android with Example
Kotlin Setters and Getters