How to Generate Route Between Two Locations in Google Map in Android?
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
- 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 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
// w3wiki 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: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:
Contact Us