Skip to content

Navigation

Gradle and AndroidManifest configure

Add dependencies below to build.gradle module app

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

allprojects {
    repositories {
        google()
        maven { url "https://jitpack.io" }
    }
}
Upgrade the compileSdk and targetSdk to version 34
compileSdk 34
targetSdk 34

Upgrade the minSdk to version 24

minSdk 24
Add the necessary permission request to the AndroidManifest.xml file
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

For android 14 and above - Add the service to the AndroidManifest.xml file (in <application> tag)

    <service
        android:name="vn.vietmap.services.android.navigation.v5.navigation.NavigationService"
        android:enabled="true"
        android:exported="false"
        android:foregroundServiceType="location" />

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
    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<String?>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
            permissionsManager!!.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    
        override fun onExplanationNeeded(permissionsToExplain: List<String?>?) {
            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

    <Button
        android:id="@+id/pushToNavigationScreen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="36dp"
        android:text="Navigation Screen"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.494"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
         />

    <TextView
        android:id="@+id/guideText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="36dp"
        android:layout_marginLeft="15dp"
        android:textSize="16dp"
        android:layout_marginRight="15dp"
        app:layout_constraintTop_toBottomOf="@+id/pushToNavigationScreen"
        app:layout_constraintStart_toStartOf="parent"
        android:text="@string/tts_guide"
        />

    <Button
        android:id="@+id/testSpeech"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="36dp"
        android:text="System text to speech setting"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.494"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/guideText"
        />

    <Button
        android:id="@+id/speechAgain"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="36dp"
        android:text="Speech again"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.494"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/testSpeech"
        />

Create a navigation activity

Create new VietMapNavigationActivity Add below code to xml file of created activity

    <vn.vietmap.vietmapsdk.maps.MapView
        android:id="@+id/ktMapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:vietmap_cameraZoom="1"
        android:visibility="visible"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHeight_percent="1"/>


    <Button
        android:id="@+id/btnStopNavigation"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_gravity="end|bottom"
        android:layout_marginRight="10dp"
        android:layout_marginBottom="10dp"
        app:layout_constraintBottom_toTopOf="@id/btnOverview"
        android:onClick="btnStartStop_click"
        android:text="StopNavigation" />

    <Button
        android:id="@+id/btnOverview"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_gravity="start|bottom"
        android:layout_marginBottom="10dp"
        android:layout_marginRight="10dp"
        android:onClick="btnStartStop_click"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/btnRecenter"
        android:text="Overview" />

    <Button
        android:id="@+id/btnRecenter"
        android:layout_marginRight="10dp"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_gravity="start|bottom"
        android:layout_marginBottom="10dp"
        android:onClick="btnStartStop_click"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toTopOf="@id/btnStartNavigation"
        android:text="ReCenter" />
    <Button
        android:id="@+id/btnStartNavigation"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_gravity="center|bottom"
        android:layout_marginBottom="10dp"
        android:layout_marginRight="10dp"
        android:onClick="btnStartStop_click"
        android:text="StartNavigation"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

Activity needs to implement some of the following Listener classes to catch user events, navigation event and process them.

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
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
    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<DirectionsRoute>? = 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

Call necessary function in onCreate callback

    @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

    // 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<Point> =
                currentRoute?.routeOptions()?.coordinates() as List<Point>
            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

    /// 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<Point>) {
        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<Point>
    ): CameraUpdate {
        val routeBounds = convertRoutePointsToLatLngBounds(routePoints)
        return CameraUpdateFactory.newLatLngBounds(
            routeBounds, padding[0], padding[1], padding[2], padding[3]
        )
    }

    private fun convertRoutePointsToLatLngBounds(routePoints: List<Point>): LatLngBounds {
        val latLngs: MutableList<LatLng> = 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
    } 

Fetch route between 2 point, with bearing

    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<DirectionsResponse> {
            override fun onResponse(
                call: Call<DirectionsResponse?>, response: Response<DirectionsResponse?>
            ) {
                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<Point> =
                    currentRoute?.routeOptions()?.coordinates() as List<Point>
                animateVietmapGLForRouteOverview(padding, routePoints)
                //Start Navigation again from new Point, if it was already in Progress
                if (isNavigationInProgress || isStartNavigation) {
                    startNavigation()
                }
            }

            override fun onFailure(call: Call<DirectionsResponse?>, throwable: Throwable) {
                isBuildingRoute = false

            }
        })
    }

Some useful functions

    /// 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<BannerInstructions> =
            routeProgress.currentLegProgress().currentStep().bannerInstructions() as List<BannerInstructions>

        /// 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. You can get the apikey at https://maps.vietmap.vn/

facebook
Tổng đài hỗ trợ
089.616.4567
facebook Chat Facebook zalo Chat Zalo