Bỏ qua

Dẫn đường

Cấu hình Gradle và AndroidManifest

Thêm các dependency sau vào build.gradle của 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'
Khai báo repository jitpack trong file settings.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' }
    }
}

Với dự án cũ, đặt khai báo này vào file build.gradle cấp project

allprojects {
    repositories {
        google()
        maven { url "https://jitpack.io" }
    }
}
Nâng compileSdk và targetSdk lên 34
compileSdk 34
targetSdk 34

Nâng minSdk lên 24

minSdk 24
Khai báo các quyền cần thiết trong file AndroidManifest.xml
    <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" />

Với Android 14 trở lên - Khai báo service trong file AndroidManifest.xml (bên trong thẻ <application>)

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

Xin quyền vị trí trong MainActivity

  • Thêm hàm kiểm tra quyền vị trí rồi mở activity dẫn đường
  • Đặt bộ đọc văn bản (text-to-speech) của hệ thống là Google; SDK dùng bộ đọc của hệ thống để đọc hướng dẫn rẽ
    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()
            }
        }
    }
    

Trong file activity_main.xml, thêm nút bấm

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

Tạo activity dẫn đường

Tạo activity mới VietMapNavigationActivity Thêm đoạn sau vào file layout xml của activity vừa tạo

    <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 cần implement các Listener sau để nhận và xử lý sự kiện từ người dùng và từ quá trình dẫn đường.

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 các package sau vào 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
Khai báo các biến cần dùng
    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()

Class CurrentCenterPoint nằm trong thư mục model của project mẫu, xem tại đây

Gọi các hàm cần thiết trong onCreate

    @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()
        }
    }

Thêm các hàm cần thiết

    // 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 các hàm cần thiết

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

Muốn dùng màu và icon riêng, tạo bản đồ trong Map Editor rồi thay URL style ở trên bằng link style của bản đồ đó.

Lấy tuyến đường giữa 2 điểm, có hướng di chuyển (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

            }
        })
    }

Một số hàm hữu ích

    /// 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
    }
Mọi thông tin trong quá trình dẫn đường được trả về qua onProgressChange

Thêm API key và styleUrl

Thay mọi chỗ YOUR_API_KEY_HERE bằng API key VIETMAP cấp cho bạn, nếu không ứng dụng sẽ crash khi chạy. Lấy API key: đăng ký miễn phí trên VIETMAP Console.

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