Source: https://maps.vietmap.vn/docs/sdk-mobile/sdk-android/navigation/
# Navigation
### Gradle and AndroidManifest configure
### Add dependencies below to build.gradle module app
```gradle
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.cardview:cardview:1,0,0"
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "com.google.android.gms:play-services-location:21.0.1"
implementation "com.jakewharton:butterknife:10.2.3"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'com.github.vietmap-company:maps-sdk-android:2.6.0'
implementation 'com.github.vietmap-company:maps-sdk-navigation-ui-android:2.3.2'
implementation 'com.github.vietmap-company:maps-sdk-navigation-android:2.3.3'
implementation 'com.github.vietmap-company:vietmap-services-core:1.0.0'
implementation 'com.github.vietmap-company:vietmap-services-directions-models:1.0.1'
implementation 'com.github.vietmap-company:vietmap-services-turf-android:1.0.2'
implementation 'com.github.vietmap-company:vietmap-services-android:1.1.2'
implementation 'com.squareup.picasso:picasso:2.8'
implementation 'com.github.vietmap-company:vietmap-services-geojson-android:1.0.0'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
```
Configure the **jitpack repository** in the **setting.gradle** file
```gradle
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
// Add two lines below to the repositories block (In setting.gradle file)
maven { url 'https://plugins.gradle.org/m2' }
maven { url 'https://jitpack.io' }
}
}
```
With older projects, place it to the **build.gradle** file at module project
```gradle
allprojects {
repositories {
google()
maven { url "https://jitpack.io" }
}
}
```
Upgrade the **compileSdk** and **targetSdk** to version **34**
```gradle
compileSdk 34
```
```gradle
targetSdk 34
```
Upgrade the **minSdk** to version **24**
```gradle
minSdk 24
```
Add the necessary permission request to the AndroidManifest.xml file
```xml
```
For android 14 and above
- Add the service to the AndroidManifest.xml file (in `` tag)
```xml
```
### Request location permission in MainActivity
- Add the function to check location permission and push to the navigation activity
- Configure system speech engine to google, our SDK use system speech engine to speak out the instruction
```java
class MainActivity : AppCompatActivity(), PermissionsListener {
private var permissionsManager: PermissionsManager? = null
private var textToSpeech: TextToSpeech? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.pushToNavigationScreen)
val ttsButton: Button = findViewById(R.id.testSpeech)
val speechAgain: Button = findViewById(R.id.speechAgain)
val intent = Intent(this, VietMapNavigationExampleV2::class.java)
button.setOnClickListener {
startActivity(intent)
speechAgain.visibility = GONE
}
speechAgain.setOnClickListener { speakOut("Language: Vietnamese") }
ttsButton.setOnClickListener {
startActivity(Intent("com.android.settings.TTS_SETTINGS"))
speechAgain.visibility = VISIBLE
}
permissionsManager = PermissionsManager(this)
if (!PermissionsManager.areLocationPermissionsGranted(this)) {
permissionsManager!!.requestLocationPermissions(this)
}
textToSpeech = TextToSpeech(
applicationContext
) { setTextToSpeechLanguage() }
}
private fun setTextToSpeechLanguage() {
val language = Locale("vi", "VN")
when (textToSpeech!!.setLanguage(language)) {
TextToSpeech.LANG_MISSING_DATA -> {
Toast.makeText(this, "Missing language data", Toast.LENGTH_LONG).show()
return
}
TextToSpeech.LANG_NOT_SUPPORTED -> {
Toast.makeText(
this,
"Language not supported " + language.language,
Toast.LENGTH_LONG
).show()
return
}
else -> {
Toast.makeText(this, "Language: Vietnamese", Toast.LENGTH_LONG).show()
speakOut("Language: Vietnamese")
}
}
}
private fun speakOut(speechContent: String) {
val utteranceId: String = UUID.randomUUID().toString()
textToSpeech!!.speak(speechContent, TextToSpeech.QUEUE_FLUSH, null, utteranceId)
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionsManager!!.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onExplanationNeeded(permissionsToExplain: List?) {
Toast.makeText(
this, "This app needs location permissions in order to show its functionality.",
Toast.LENGTH_LONG
).show()
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
} else {
Toast.makeText(
this, "You didn't grant location permissions.",
Toast.LENGTH_LONG
).show()
}
}
}
```
In **activity_main.xml** file, add button layout
```js
```
### Create a navigation activity
Create new **VietMapNavigationActivity**
Add below code to **xml** file of created activity
```js
```
Activity needs to implement some of the following Listener classes to catch user events, navigation event and process them.
```js
class VietMapNavigationExampleV2 : AppCompatActivity() , OnMapReadyCallback, ProgressChangeListener,
OffRouteListener, MilestoneEventListener, NavigationEventListener, NavigationListener,
FasterRouteListener, SpeechAnnouncementListener, BannerInstructionsListener, RouteListener,
VietMapGL.OnMapLongClickListener, VietMapGL.OnMapClickListener,
MapView.OnDidFinishRenderingMapListener{
override fun onCreate(savedInstanceState: Bundle?) {
// this function must be called before super.onCreate(savedInstanceState), otherwise the app will crash
Vietmap.getInstance(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_viet_map_navigation_example_v2)
}
}
```
Import below packages to the activity
```js
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.content.pm.PackageManager
import android.graphics.Color.blue
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import androidx.core.app.ActivityCompat
import androidx.core.content.res.ResourcesCompat
import com.example.model.CurrentCenterPoint
import com.example.ultis.IconUtils
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.mapbox.api.directions.v5.models.BannerInstructions
import com.mapbox.api.directions.v5.models.DirectionsResponse
import com.mapbox.api.directions.v5.models.DirectionsRoute
import com.mapbox.geojson.Point
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import timber.log.Timber
import vn.vietmap.services.android.navigation.ui.v5.camera.CameraOverviewCancelableCallback
import vn.vietmap.services.android.navigation.ui.v5.listeners.BannerInstructionsListener
import vn.vietmap.services.android.navigation.ui.v5.listeners.NavigationListener
import vn.vietmap.services.android.navigation.ui.v5.listeners.RouteListener
import vn.vietmap.services.android.navigation.ui.v5.listeners.SpeechAnnouncementListener
import vn.vietmap.services.android.navigation.ui.v5.voice.NavigationSpeechPlayer
import vn.vietmap.services.android.navigation.ui.v5.voice.SpeechAnnouncement
import vn.vietmap.services.android.navigation.ui.v5.voice.SpeechPlayer
import vn.vietmap.services.android.navigation.ui.v5.voice.SpeechPlayerProvider
import vn.vietmap.services.android.navigation.v5.location.engine.LocationEngineProvider
import vn.vietmap.services.android.navigation.v5.location.replay.ReplayRouteLocationEngine
import vn.vietmap.services.android.navigation.v5.milestone.Milestone
import vn.vietmap.services.android.navigation.v5.milestone.MilestoneEventListener
import vn.vietmap.services.android.navigation.v5.milestone.VoiceInstructionMilestone
import vn.vietmap.services.android.navigation.v5.navigation.*
import vn.vietmap.services.android.navigation.v5.navigation.camera.RouteInformation
import vn.vietmap.services.android.navigation.v5.offroute.OffRouteListener
import vn.vietmap.services.android.navigation.v5.route.FasterRouteListener
import vn.vietmap.services.android.navigation.v5.routeprogress.ProgressChangeListener
import vn.vietmap.services.android.navigation.v5.routeprogress.RouteProgress
import vn.vietmap.services.android.navigation.v5.snap.SnapToRoute
import vn.vietmap.services.android.navigation.v5.utils.RouteUtils
import vn.vietmap.vietmapsdk.Vietmap
import vn.vietmap.vietmapsdk.annotations.Marker
import vn.vietmap.vietmapsdk.annotations.MarkerOptions
import vn.vietmap.vietmapsdk.camera.CameraPosition
import vn.vietmap.vietmapsdk.camera.CameraUpdate
import vn.vietmap.vietmapsdk.camera.CameraUpdateFactory
import vn.vietmap.vietmapsdk.geometry.LatLng
import vn.vietmap.vietmapsdk.geometry.LatLngBounds
import vn.vietmap.vietmapsdk.location.LocationComponent
import vn.vietmap.vietmapsdk.location.LocationComponentActivationOptions
import vn.vietmap.vietmapsdk.location.engine.LocationEngine
import vn.vietmap.vietmapsdk.location.modes.CameraMode
import vn.vietmap.vietmapsdk.location.modes.RenderMode
import vn.vietmap.vietmapsdk.maps.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
```
Define below necessary variables
```js
private var mapView: MapView? = null
private var vietmapGL: VietMapGL? = null
private var currentRoute: DirectionsRoute? = null
private var routeClicked: Boolean = false
private var locationEngine: LocationEngine? = null
private var navigationMapRoute: NavigationMapRoute? = null
private var directionsRoutes: List? = null
private val snapEngine = SnapToRoute()
private var simulateRoute = false
private var primaryRouteIndex = 0
private var fusedLocationClient: FusedLocationProviderClient? = null
private var navigation: VietmapNavigation? = null
private var speechPlayerProvider: SpeechPlayerProvider? = null
private var speechPlayer: SpeechPlayer? = null
private var routeProgress: RouteProgress? = null
private val routeUtils = RouteUtils()
private var voiceInstructionsEnabled = true
private var isBuildingRoute = false
private var origin = Point.fromLngLat(106.675789, 10.759050)
private var destination = Point.fromLngLat(106.686777, 10.775056)
private var locationComponent: LocationComponent? = null
private var currentCenterPoint: CurrentCenterPoint? = null
private var isOverviewing = false
private var animateBuildRoute = true
private var isNavigationInProgress = false
private var isNavigationCanceled = false
private var zoom = 20.0
private var bearing = 0.0
private var tilt = 0.0
private var padding: IntArray = intArrayOf(150, 500, 150, 500)
private var isRunning: Boolean = false
private var options: VietMapGLOptions? = null
private val navigationOptions =
VietmapNavigationOptions.builder().build()
```
You can find `CurrentCenterPoint` class in model folder of this project [Here](https://github.com/vietmap-company/vietmap-android-navigation-example/blob/v2.0.0/app/src/main/java/com/example/vietmapandroidnavigationexamplev2/model/CurrentCenterPoint.kt)
### Call necessary function in onCreate callback
```js
@Override
protected void onCreate(Bundle savedInstanceState) {
Vietmap.getInstance(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_viet_map_navigation_example_v2)
initLocationEngine()
speechPlayerProvider = SpeechPlayerProvider(this, "vi", true);
speechPlayer = NavigationSpeechPlayer(speechPlayerProvider)
mapView = findViewById(R.id.ktMapView)
mapView!!.onCreate(savedInstanceState)
mapView!!.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
options = VietMapGLOptions.createFromAttributes(this).compassEnabled(false)
mapView = MapView(this, options)
navigation = VietmapNavigation(
this, navigationOptions, locationEngine!!
)
mapView!!.getMapAsync(this)
val btnStopNavigation: Button = findViewById(R.id.btnStopNavigation)
btnStopNavigation.setOnClickListener {
finishNavigation()
}
val btnStartNavigation: Button = findViewById(R.id.btnStartNavigation)
btnStartNavigation.setOnClickListener {
startNavigation()
}
val btnOverview: Button = findViewById(R.id.btnOverview)
btnOverview.setOnClickListener {
overViewRoute()
}
val btnRecenter: Button = findViewById(R.id.btnRecenter)
btnRecenter.setOnClickListener {
recenter()
}
}
```
### Add some necessary function below
```js
// Overview the route on the map, show all the route on the map inside the screen
private fun overViewRoute() {
isOverviewing = true
routeProgress?.let { showRouteOverview(padding, it) }
}
/// clear all the route in the map
private fun clearRoute() {
if (navigationMapRoute != null) {
navigationMapRoute?.removeRoute()
}
currentRoute = null
}
/// init the map route and add some listener
private fun initMapRoute() {
if (vietmapGL != null) {
/// add "boundary_province" layer to map to show the route below the roadname, this layer is provided by Vietmap tile map.
navigationMapRoute = NavigationMapRoute(mapView!!, vietmapGL!!, "boundary_province")
}
/// callback when user click on another route, this function will be called and show the new route on the map
navigationMapRoute?.setOnRouteSelectionChangeListener {
routeClicked = true
currentRoute = it
val routePoints: List =
currentRoute?.routeOptions()?.coordinates() as List
animateVietmapGLForRouteOverview(padding, routePoints)
primaryRouteIndex = try {
it.routeIndex()?.toInt() ?: 0
} catch (e: Exception) {
0
}
if (isRunning) {
finishNavigation(isOffRouted = true)
startNavigation()
}
}
vietmapGL?.addOnMapClickListener(this)
}
/// stop current navigation
private fun finishNavigation(isOffRouted: Boolean = false) {
zoom = 15.0
bearing = 0.0
tilt = 0.0
isNavigationCanceled = true
if (!isOffRouted) {
isNavigationInProgress = false
moveCameraToOriginOfRoute()
}
if (currentRoute != null) {
isRunning = false
navigation!!.stopNavigation()
navigation!!.removeFasterRouteListener(this)
navigation!!.removeMilestoneEventListener(this)
navigation!!.removeNavigationEventListener(this)
navigation!!.removeOffRouteListener(this)
navigation!!.removeProgressChangeListener(this)
}
}
private fun moveCameraToOriginOfRoute() {
currentRoute?.let {
try {
val originCoordinate = it.routeOptions()?.coordinates()?.get(0)
originCoordinate?.let {
val location = LatLng(originCoordinate.latitude(), originCoordinate.longitude())
moveCamera(location, null)
}
} catch (e: java.lang.Exception) {
Timber.i(String.format("moveCameraToOriginOfRoute, %s", "Error: ${e.message}"))
}
}
}
/// move camera to a specific location with bearing
private fun moveCamera(location: LatLng, bearing: Float?) {
val cameraPosition = CameraPosition.Builder().target(location).zoom(zoom).tilt(tilt)
if (bearing != null) {
cameraPosition.bearing(bearing.toDouble())
}
var duration = 3000
if (!animateBuildRoute) duration = 1
vietmapGL?.animateCamera(
CameraUpdateFactory.newCameraPosition(cameraPosition.build()), duration
)
}
/// start navigation with current selected route
private fun startNavigation() {
tilt = 60.0
zoom = 19.0
isOverviewing = false
isNavigationCanceled = false
vietmapGL?.locationComponent?.cameraMode = CameraMode.TRACKING_GPS_NORTH
if (currentRoute != null) {
if (simulateRoute) {
val mockLocationEngine = ReplayRouteLocationEngine()
mockLocationEngine.assign(currentRoute)
navigation!!.locationEngine = mockLocationEngine
} else {
locationEngine?.let {
navigation!!.locationEngine = it
}
}
isRunning = true
vietmapGL?.locationComponent?.locationEngine = null
navigation!!.addNavigationEventListener(this)
navigation!!.addFasterRouteListener(this)
navigation!!.addMilestoneEventListener(this)
navigation!!.addOffRouteListener(this)
navigation!!.addProgressChangeListener(this)
navigation!!.snapEngine = snapEngine
currentRoute?.let {
isNavigationInProgress = true
navigation!!.startNavigation(currentRoute!!)
recenter()
}
}
}
/// recenter the map to current location in the route
private fun recenter() {
isOverviewing = false
if (currentCenterPoint != null) {
moveCamera(
LatLng(currentCenterPoint!!.latitude, currentCenterPoint!!.longitude),
currentCenterPoint!!.bearing
)
}
}
/// init location engine to get current location of user
/// mock location engine is used to simulate route, will used in demo and test case
/// location engine provider is used to get current location of user, will used in real case
private fun initLocationEngine() {
locationEngine = if (simulateRoute) {
ReplayRouteLocationEngine()
} else {
LocationEngineProvider.getBestLocationEngine(this)
}
}
```
### Implement some necessary function
```js
/// this function will be called when map is ready to use, you can add some listener here to handle user interaction with map
override fun onMapReady(p0: VietMapGL) {
vietmapGL = p0
vietmapGL!!.setStyle(
Style.Builder()
.fromUri("https://maps.vietmap.vn/api/maps/light/styles.json?apikey=YOUR_API_KEY_HERE")
) { style: Style? ->
initLocationEngine()
enableLocationComponent(style)
initMapRoute()
}
vietmapGL!!.addOnMapClickListener(this)
vietmapGL!!.addOnMapLongClickListener(this)
}
/// init the location component to start tracking user location and show it on the map
/// using location engine to get current location of user
private fun enableLocationComponent(style: Style?) {
locationComponent = vietmapGL!!.locationComponent
if (locationComponent != null) {
locationComponent!!.activateLocationComponent(
LocationComponentActivationOptions.builder(
this, style!!
).build()
)
if (checkPermission()) {
return
}
locationComponent!!.setCameraMode(
CameraMode.TRACKING_GPS_NORTH, 750L, 18.0, 10000.0, 10000.0, null
)
locationComponent!!.isLocationComponentEnabled = true
locationComponent!!.zoomWhileTracking(19.0)
locationComponent!!.renderMode = RenderMode.GPS
locationComponent!!.locationEngine = locationEngine
}
}
override fun onMapClick(p0: LatLng): Boolean {
addMarker(p0)
// handle on Map click here
return false
}
/// show route overview
private fun showRouteOverview(padding: IntArray?, currentRouteProgress: RouteProgress) {
val routeInformation: RouteInformation =
buildRouteInformationFromProgress(currentRouteProgress)
animateCameraForRouteOverview(routeInformation, padding!!)
}
private fun buildRouteInformationFromProgress(routeProgress: RouteProgress?): RouteInformation {
return if (routeProgress == null) {
RouteInformation.create(null, null, null)
} else RouteInformation.create(routeProgress.directionsRoute(), null, null)
}
private fun animateCameraForRouteOverview(
routeInformation: RouteInformation, padding: IntArray
) {
val cameraEngine = navigation!!.cameraEngine
val routePoints = cameraEngine.overview(routeInformation)
if (routePoints.isNotEmpty()) {
animateVietmapGLForRouteOverview(padding, routePoints)
}
}
private fun animateVietmapGLForRouteOverview(padding: IntArray, routePoints: List) {
if (routePoints.size <= 1) {
return
}
val resetUpdate: CameraUpdate = buildResetCameraUpdate()
val overviewUpdate: CameraUpdate = buildOverviewCameraUpdate(padding, routePoints)
vietmapGL?.animateCamera(
resetUpdate, 150, CameraOverviewCancelableCallback(overviewUpdate, vietmapGL)
)
}
private fun buildResetCameraUpdate(): CameraUpdate {
val resetPosition: CameraPosition = CameraPosition.Builder().tilt(0.0).bearing(0.0).build()
return CameraUpdateFactory.newCameraPosition(resetPosition)
}
private fun buildOverviewCameraUpdate(
padding: IntArray, routePoints: List
): CameraUpdate {
val routeBounds = convertRoutePointsToLatLngBounds(routePoints)
return CameraUpdateFactory.newLatLngBounds(
routeBounds, padding[0], padding[1], padding[2], padding[3]
)
}
private fun convertRoutePointsToLatLngBounds(routePoints: List): LatLngBounds {
val latLngs: MutableList = ArrayList()
for (routePoint in routePoints) {
latLngs.add(LatLng(routePoint.latitude(), routePoint.longitude()))
}
return LatLngBounds.Builder().includes(latLngs).build()
}
override fun onMilestoneEvent(p0: RouteProgress?, p1: String?, p2: Milestone?) {
if (voiceInstructionsEnabled) {
playVoiceAnnouncement(p2)
}
if (p0 != null && p2 != null) {
if (routeUtils.isArrivalEvent(p0, p2) && isNavigationInProgress) {
vietmapGL?.locationComponent?.locationEngine = locationEngine
finishNavigation()
}
}
}
private fun playVoiceAnnouncement(milestone: Milestone?) {
if (milestone is VoiceInstructionMilestone) {
var announcement = SpeechAnnouncement.builder()
.voiceInstructionMilestone(milestone as VoiceInstructionMilestone?).build()
speechPlayer!!.play(announcement)
}
}
override fun onRunning(p0: Boolean) {
/// Handle navigation running events here
}
override fun onCancelNavigation() {
/// Handle a navigation cancel event here
}
override fun onNavigationFinished() {
/// Handle a navigation finished event here
}
override fun onNavigationRunning() {
/// Handle a navigation running event here
}
override fun fasterRouteFound(p0: DirectionsRoute?) {
p0?.let {
currentRoute = p0
finishNavigation()
startNavigation()
}
}
override fun willVoice(p0: SpeechAnnouncement?): SpeechAnnouncement {
/// return null if you turn off voice instruction
return p0!!
}
override fun willDisplay(p0: BannerInstructions?): BannerInstructions {
/// return null if you turn off banner instruction
return p0!!
}
override fun allowRerouteFrom(p0: Point?): Boolean {
return true
}
override fun onOffRoute(offRoutePoint: Point?) {
doOnNewRoute(offRoutePoint)
}
override fun onRerouteAlong(p0: DirectionsRoute?) {
p0?.let {
currentRoute = p0
finishNavigation()
startNavigation()
}
}
override fun onFailedReroute(p0: String?) {
// handle failed reroute here
}
override fun onArrival() {
vietmapGL?.locationComponent?.locationEngine = locationEngine
// handle arrival here
println("You have arrived at your destination")
}
override fun onMapLongClick(latLng: LatLng): Boolean {
getCurrentLocation()
destination = Point.fromLngLat(latLng.longitude, latLng.latitude)
if (origin != null) {
fetchRouteWithBearing(false)
}
return false
}
override fun onDidFinishRenderingMap(p0: Boolean) {
/// Handle did finish rendering map event here
}
private fun getCurrentLocation() {
if (checkPermission()) {
fusedLocationClient!!.lastLocation.addOnSuccessListener(
this
) { location: Location? ->
if (location != null) {
origin = Point.fromLngLat(
location.longitude, location.latitude
)
}
}
}
}
private fun addMarker(position:LatLng ): Marker {
return vietmapGL!!.addMarker(
MarkerOptions()
.position(position)
.icon(
IconUtils().drawableToIcon(
this,
R.drawable.continue_straight,
ResourcesCompat.getColor(resources,R.color.blue , theme)
)
)
)
}
private fun checkPermission(): Boolean {
return ActivityCompat.checkSelfPermission(
this, ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this, ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
```
To use your own colors and icons, create a map in [Map Editor](https://tools.vietmap.vn/map-editor/) and replace the style URL above with its style link.
### Fetch route between 2 point, with bearing
```js
private fun fetchRouteWithBearing(isStartNavigation: Boolean) {
if (checkPermission()) {
fusedLocationClient?.lastLocation?.addOnSuccessListener(
this
) { location: Location? ->
if (location != null) {
fetchRoute(isStartNavigation, location.bearing)
}
}
} else {
fetchRoute(isStartNavigation, null)
}
}
private fun fetchRoute(isStartNavigation: Boolean, bearing: Float?) {
val builder =
NavigationRoute.builder(this).apikey("YOUR_API_KEY_HERE")
.origin(origin, bearing?.toDouble(), bearing?.toDouble())
.destination(destination, bearing?.toDouble(), bearing?.toDouble())
builder.build().getRoute(object : Callback {
override fun onResponse(
call: Call, response: Response
) {
directionsRoutes = response.body()!!.routes()
currentRoute = if (directionsRoutes!!.size <= primaryRouteIndex) {
directionsRoutes!![0]
} else {
directionsRoutes!![primaryRouteIndex]
}
// Draw the route on the map
if (navigationMapRoute != null) {
navigationMapRoute?.removeRoute()
} else {
navigationMapRoute =
NavigationMapRoute(mapView!!, vietmapGL!!, "boundary_province")
}
//show multiple route to map
if (response.body()!!.routes().size > 1) {
navigationMapRoute?.addRoutes(directionsRoutes!!)
} else {
navigationMapRoute?.addRoute(currentRoute)
}
isBuildingRoute = false
// get route point from current route
val routePoints: List =
currentRoute?.routeOptions()?.coordinates() as List
animateVietmapGLForRouteOverview(padding, routePoints)
//Start Navigation again from new Point, if it was already in Progress
if (isNavigationInProgress || isStartNavigation) {
startNavigation()
}
}
override fun onFailure(call: Call, throwable: Throwable) {
isBuildingRoute = false
}
})
}
```
### Some useful functions
```js
/// this function will response to the progress change of navigation, which contain all of data while user is in navigation
override fun onProgressChange(location: Location?, routeProgress: RouteProgress?) {
/// Get current speed of user by using location?.speed
// location?.speed
/*
val bannerInstructionsList: List =
routeProgress.currentLegProgress().currentStep().bannerInstructions() as List
/// the modifier and type will guide you to the next turn direction
currentModifier = bannerInstructionsList?.get(0)?.primary()?.modifier()
currentModifierType= bannerInstructionsList?.get(0)?.primary()?.type()
// val util = RouteUtils()
// arrived = util.isArrivalEvent(routeProgress) && util.isLastLeg(routeProgress)
/// You can get the distance remaining to destination by using
distanceRemaining = routeProgress.distanceRemaining().toFloat()
/// You can get the duration remaining to destination by using
durationRemaining = routeProgress.durationRemaining()
/// You can get the distance traveled by using
distanceTraveled = routeProgress.distanceTraveled().toFloat()
legIndex = routeProgress.currentLegProgress()?.stepIndex()
// stepIndex = routeProgress.stepIndex
val leg = routeProgress.currentLeg()
if (leg != null)
currentLeg = VietMapRouteLeg(leg)
/// You can get the current step guide text by using
currentStepInstruction = bannerInstructionsList?.get(0)
?.primary()
?.text()
/// You can get the distance traveled from the last turn by using
currentLegDistanceTraveled = routeProgress.currentLegProgress()?.distanceTraveled()?.toFloat()
currentLegDistanceRemaining = routeProgress.currentLegProgress()?.distanceRemaining()?.toFloat()
/// You can get the distance remaining to the next turn by using
distanceToNextTurn = routeProgress.stepDistanceRemaining().toFloat()
*/
if (!isNavigationCanceled && location != null && routeProgress != null) {
try {
val noRoutes: Boolean = directionsRoutes?.isEmpty() ?: true
val newCurrentRoute: Boolean = !routeProgress!!.directionsRoute()
.equals(directionsRoutes?.get(primaryRouteIndex))
val isANewRoute: Boolean = noRoutes || newCurrentRoute
if (isANewRoute) {
} else {
currentCenterPoint =
CurrentCenterPoint(location.latitude, location.longitude, location.bearing)
/// update the map camera to current location in realtime, if user is not overviews the route
if (!isOverviewing) {
this.routeProgress = routeProgress
moveCamera(LatLng(location.latitude, location.longitude), location.bearing)
}
/// snap the location of user to the route, which always show the location of user on the route
if (!isBuildingRoute) {
val snappedLocation: Location =
snapEngine.getSnappedLocation(location, routeProgress)
vietmapGL?.locationComponent?.forceLocationUpdate(snappedLocation)
}
if (simulateRoute && !isBuildingRoute) {
vietmapGL?.locationComponent?.forceLocationUpdate(location)
}
}
/// This function will calculate when the user is near the next turn, and make the map tilt to 0 degree, which help user easy to find the next turn
handleProgressChange(location, routeProgress)
} catch (e: java.lang.Exception) {
}
}
}
private fun handleProgressChange(location: Location, routeProgress: RouteProgress) {
val distanceRemainingToNextTurn =
routeProgress.currentLegProgress()?.currentStepProgress()?.distanceRemaining()
if (distanceRemainingToNextTurn != null && distanceRemainingToNextTurn < 30) {
val resetPosition: CameraPosition =
CameraPosition.Builder().tilt(0.0).zoom(17.0).build()
val cameraUpdate = CameraUpdateFactory.newCameraPosition(resetPosition)
vietmapGL?.animateCamera(
cameraUpdate, 1000
)
} else {
if (routeProgress.currentLegProgress().currentStepProgress()
.distanceTraveled() > 30 && !isOverviewing
) {
recenter()
}
}
}
override fun userOffRoute(location: Location?) {
location?.let {
if (checkIfUserOffRoute(it)) {
speechPlayer!!.onOffRoute()
doOnNewRoute(Point.fromLngLat(location.longitude, location.latitude))
}
}
}
private fun doOnNewRoute(offRoutePoint: Point?) {
if (!isBuildingRoute) {
isBuildingRoute = true
offRoutePoint?.let {
finishNavigation(isOffRouted = true)
moveCamera(LatLng(it.latitude(), it.longitude()), null)
}
origin = offRoutePoint
isNavigationInProgress = true
fetchRouteWithBearing(false)
}
}
private fun checkIfUserOffRoute(location: Location): Boolean {
val snapLocation: Location = snapEngine.getSnappedLocation(location, routeProgress)
val distance: Double = calculateDistanceBetween2Point(location, snapLocation)
return distance > 30
}
private fun calculateDistanceBetween2Point(location1: Location, location2: Location): Double {
/// this function calculate distance between 2 point in the earth, with planetary arc
val radius = 6371000.0 // meters
val dLat = (location2.latitude - location1.latitude) * PI / 180.0
val dLon = (location2.longitude - location1.longitude) * PI / 180.0
val a =
sin(dLat / 2.0) * sin(dLat / 2.0) + cos(location1.latitude * PI / 180.0) * cos(location2.latitude * PI / 180.0) * sin(
dLon / 2.0
) * sin(dLon / 2.0)
val c = 2.0 * kotlin.math.atan2(sqrt(a), sqrt(1.0 - a))
return radius * c
}
```
All information about navigation will response in `onProgressChange`
## Add apikey and styleUrl
To ensure that the application does not crash when running, you need to add the apikey that VietMap provides at the `YOUR_API_KEY_HERE` keyword to use the SDK. Get an API key by [registering for free on VIETMAP Console](https://maps.vietmap.vn/console-v2/register).