Navigation use case

VERSION 0.3.1106
PUBLIC PREVIEW

Navigation SDK for Android is only available upon request. Contact us to get started.

This tutorial shows how to build a simple navigation application using the TomTom Navigation SDK for Android. The app uses the built-in UI components. However, you can build custom components and integrate them with the SDK.

The application displays a map and shows the user’s location. After the user selects a destination with a long click, the app plans a route and draws it on the map. Navigation is started once the user taps on the route.

Getting started

  1. Install Android Studio if you don’t already have it.
  2. Create a new project or open an existing one. Make sure that the minimum SDK API level is set to at least 21 (Android 5.0 "Lollipop") and that the compile SDK API level is set to 31.
  3. Add the packagingOptions.
    1android {
    2 ...
    3 packagingOptions {
    4 pickFirst "lib/**/libc++_shared.so"
    5 }
    6}
  4. Because the repository for Navigation SDK is private, you will need to contact us to get access.
  5. Once you have obtained access, go to repositories.tomtom.com and log in with your account. Expand the user menu in the top-right corner, and select "Edit profile" → "Generate an Identity Token". Copy your token and put it, together with your login, in the settings.gradle file of your project, replacing the repositoryUsername and repositoryToken placeholders.
    1dependencyResolutionManagement {
    2 repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    3 repositories {
    4 google()
    5 mavenCentral()
    6 maven {
    7 credentials {
    8 username = repositoryUsername
    9 password = repositoryToken
    10 }
    11 url = uri("https://repositories.tomtom.com/artifactory/maven")
    12 }
    13 }
    14}
  6. In the app/build.gradle file, add dependencies to the Map Display, Routing, Location, and Navigation modules.
    1dependencies {
    2 def version = "0.3.1106"
    3 implementation "com.tomtom.sdk.maps:map-display:$version"
    4 implementation "com.tomtom.sdk.routing:route-planner-online:$version"
    5 implementation "com.tomtom.sdk.location:provider-android:$version"
    6 implementation "com.tomtom.sdk.location:provider-simulation:$version"
    7 implementation "com.tomtom.sdk.location:provider-map-matched:$version"
    8 implementation "com.tomtom.sdk.navigation:navigation:$version"
    9 implementation "com.tomtom.sdk.navigation:ui:$version"
    10 implementation "com.tomtom.sdk.navigation:route-replanner-default:$version"
    11}
  7. Add the appropriate TomTom API key. If you don’t have an API Key, go to How to get a TomTom API Key to learn how to create one.

Displaying a map

Once the project is set up with the appropriate dependencies, you can initialize the map. To initialize a map, prepare the FragmentContainerView for the map. The map is displayed within this layout.

1<androidx.fragment.app.FragmentContainerView
2 android:id="@+id/map_container"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent" />
  • MapOptions is required to initialize the map.
  • Use MapFragment to display a map.
  • Optional configuration: You can further configure the map by setting various properties of the MapOptions object. You can learn more in the Map Configuration guide.
  • The last step is adding the MapFragment to the previously created container.

Any interaction with the map, such as adding a marker or drawing a route, is made via the TomTomMap object. Since map initialization can take a while, retrieving the TomTomMap object has to be done asynchronously. Keep the instance of it for further actions.

1private fun initMap() {
2 val mapOptions = MapOptions(mapKey = "YOUR_MAP_API_KEY")
3 mapFragment = MapFragment.newInstance(mapOptions)
4 supportFragmentManager.beginTransaction()
5 .replace(R.id.map_container, mapFragment)
6 .commit()
7 mapFragment.getMapAsync { map ->
8 tomTomMap = map
9 enableUserLocation()
10 setUpMapListeners()
11 }
12}

center

Showing user location

Showing the user’s location is crucial in a navigation application. To do this, the application must use the device’s location services, which requires the appropriate permissions. The Android system requires the user to grant permissions for ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION.

On devices that run Android 6.0 or higher, you must request permissions at runtime. Learn how to grant location permissions in the Android Request location permissions document.

The TomTom SDK provides a LocationProvider interface that is used between different modules to get location updates. This tutorial uses the AndroidLocationProvider. Under the hood, the engine uses Android’s system location services. NOTE: to obtain location updates, the LocationProvider.enable() method must be called. To learn more about the LocationProvider, consult the Location quickstart guide.

1private fun initLocationProvider() {
2 locationProvider = AndroidLocationProvider(context = this)
3}

The LocationProvider itself only reports location changes. It does not interact internally with the map or navigation. Therefore, to show the user’s location on the map you have to set the LocationProvider to the TomTomMap. You also have to manually enable the location indicator. It can be configured using the LocationMarkerOptions class. Read more about user location on the map in the Showing User Location document.

1private fun showUserLocation() {
2 locationProvider.enable()
3 tomTomMap.setLocationProvider(locationProvider)
4 val locationMarker = LocationMarkerOptions(type = LocationMarkerOptions.Type.POINTER)
5 tomTomMap.enableLocationMarker(locationMarker)
6}

center

Generating a route

This section describes how to calculate a route between two locations and display it on the map. You can find more details on adjusting planning criteria and accommodating vehicle profiles in the routing documentation.

In this tutorial on generating a route, the origin is the user’s location and the destination is determined by the user selecting a location on the map. To mark the destination on the map, add the OnMapLongClickListener event handler to the map view. The method removes all polygons, circles, routes, and markers that were previously added to the map. It then creates a route between the user’s location and the selected location. The method needs to return a boolean value when the callback is consumed.

1private val onMapLongClickListener = OnMapLongClickListener { geoPoint ->
2 clearMap()
3 calculateRouteTo(geoPoint)
4 true
5}
6
7private fun setUpMapListeners() {
8 tomTomMap.addOnMapLongClickListener(onMapLongClickListener)
9}

Make sure to add listeners when the map instance is ready to be used.

1mapFragment.getMapAsync { map ->
2 tomTomMap = map
3 enableUserLocation()
4 setUpMapListeners()
5}
see full method
1 private fun initMap() {
2 val mapOptions = MapOptions(mapKey = "YOUR_MAP_API_KEY")
3 mapFragment = MapFragment.newInstance(mapOptions)
4 supportFragmentManager.beginTransaction()
5 .replace(R.id.map_container, mapFragment)
6 .commit()
7 mapFragment.getMapAsync { map ->
8 tomTomMap = map
9 enableUserLocation()
10 setUpMapListeners()
11 }
12 }

The entry point for the routing service is the Routing API interface. To initialize it, use the default TomTom implementation based on TomTom’s Routing API.

1private fun initRouting() {
2 routePlanner =
3 OnlineRoutePlanner.create(context = this, apiKey = "YOUR_ROUTING_API_KEY")
4 routeReplanner = DefaultRouteReplanner.create(routePlanner)
5}

You can plan a route using RoutePlanningOptions and handle the planning result with RoutePlanningCallback:

routePlanner.planRoute(routePlanningOptions, routePlanningCallback)
see full method
1 private fun calculateRouteTo(destination: GeoPoint) {
2 val userLocation =
3 tomTomMap.currentLocation?.position ?: return
4 val itinerary = Itinerary(origin = userLocation, destination = destination)
5 routePlanningOptions = RoutePlanningOptions(
6 itinerary = itinerary,
7 guidanceOptions = GuidanceOptions(
8 instructionType = InstructionType.Text,
9 phoneticsType = InstructionPhoneticsType.Ipa,
10 announcementPoints = AnnouncementPoints.All,
11 extendedSections = ExtendedSections.All,
12 progressPoints = ProgressPoints.All
13 ),
14 vehicle = Vehicle.Car()
15 )
16 routePlanner.planRoute(routePlanningOptions, routePlanningCallback)
17 }

For a better user experience during navigation, set the following guidance parameters in RoutePlanningOptions:

  • InstructionType - This indicates that the routing result has to contain guidance instructions.
  • InstructionPhoneticsType - This specifies whether to include phonetic transcriptions in the response.
  • AnnouncementPoints - When this parameter is specified, the instruction in the response includes up to three additional fine-grained announcement points, each with its own location, maneuver type, and distance to the instruction point.
  • ExtendedSections - This specifies whether to include extended guidance sections in the response, such as sections of type road shield, lane, and speed limit.
  • ProgressPoints - This specifies whether to include progress points in the response.
1routePlanningOptions = RoutePlanningOptions(
2 itinerary = itinerary,
3 guidanceOptions = GuidanceOptions(
4 instructionType = InstructionType.Text,
5 phoneticsType = InstructionPhoneticsType.Ipa,
6 announcementPoints = AnnouncementPoints.All,
7 extendedSections = ExtendedSections.All,
8 progressPoints = ProgressPoints.All
9 ),
10 vehicle = Vehicle.Car()
11)

The RoutePlanningCallback itself has two methods. The first method is triggered if the request fails. The second method returns RoutePlanningResult containing the routing results. This tutorial draws the first retrieved route on the map, using the RouteOptions class. You can show the overview of the added routes using the TomTomMap.zoomToRoutes(Int) method. Note that its padding parameter is expressed in pixels. You can read more about adding a route to the map in Route Planning and Driving document.

1private val routePlanningCallback = object : RoutePlanningCallback {
2 override fun onSuccess(result: RoutePlanningResult) {
3 route = result.routes.first()
4 drawRoute(route!!)
5 }
6
7 override fun onError(error: RoutingError) {
8 Toast.makeText(this@MainActivity, error.message, Toast.LENGTH_SHORT).show()
9 }
10
11 override fun onRoutePlanned(route: Route) = Unit
12}
13private fun drawRoute(route: Route) {
14 val instructions = route.mapInstructions()
15 val geometry = route.legs.flatMap { it.points }
16 val routeOptions = RouteOptions(
17 geometry = geometry,
18 destinationMarkerVisible = true,
19 departureMarkerVisible = true,
20 instructions = instructions
21 )
22 tomTomMap.addRoute(routeOptions)
23 tomTomMap.zoomToRoutes(ZOOM_TO_ROUTE_PADDING)
24}
25companion object {
26 private const val ZOOM_TO_ROUTE_PADDING = 100
27}

For the navigation use case, the instructions can be drawn on the route in form of arrows that indicate maneuvers. To do this, map the Instruction object provided by routing to the Instruction object used by the map. Note that during navigation, you need to update the progress property of the drawn route to display the next instructions.

1private fun Route.mapInstructions(): List<Instruction> {
2 val routeInstructions = legs.flatMap { routeLeg -> routeLeg.instructions }
3 return routeInstructions.map {
4 Instruction(
5 routeOffset = it.routeOffset,
6 combineWithNext = it.combineWithNext
7 )
8 }
9}

Route instruction

Starting navigation

This tutorial uses the built-in navigation UI and the location simulation engine to add turn-by-turn experience to your application. Using the built-in UI, the application will display upcoming manoeuvres, remaining distance, estimated time of arrival (ETA), current speed, and speed limit information. Read more about it in the Turn-by-turn navigation guide. It can be used with the provided user interface components or your custom ones. This tutorial uses the default Navigation UI in a form of the NavigationFragment.

To use navigation in the application, start by by initialising the Navigation configuration.

1private fun initNavigation() {
2 val navigationConfiguration = NavigationConfiguration(
3 context = this,
4 apiKey = "YOUR_NAVIGATION_API_KEY",
5 locationProvider = locationProvider,
6 routeReplanner = routeReplanner
7 )
8 tomTomNavigation = TomTomNavigation.create(navigationConfiguration)
9}

Next, add the NavigationFragment to display the UI navigation information.

Disposal of TomTom Navigation is not handled by the NavigationFragment. You have to call TomTomNavigation.close() on your own once the navigation is no longer needed.

1<androidx.fragment.app.FragmentContainerView
2 android:id="@+id/navigation_fragment_container"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content"
5 app:layout_constraintBottom_toBottomOf="@+id/map_container" />
1private fun initNavigationFragment() {
2 val navigationUiOptions = NavigationUiOptions(
3 keepInBackground = true
4 )
5 navigationFragment = NavigationFragment.newInstance(navigationUiOptions)
6 supportFragmentManager.beginTransaction()
7 .add(R.id.navigation_fragment_container, navigationFragment)
8 .commitNow()
9}

Start navigation by passing the Route object along which the navigation will be done, and RoutePlanningOptions used during the route planning. Note that you have to set the previously-created TomTom Navigation object to the NavigationFragment before using it.

1private fun startNavigation(route: Route) {
2 initNavigationFragment()
3 navigationFragment.setTomTomNavigation(tomTomNavigation)
4 val routePlan = RoutePlan(route, routePlanningOptions)
5 navigationFragment.startNavigation(routePlan)
6 navigationFragment.addNavigationListener(navigationListener)
7 tomTomNavigation.addOnProgressUpdateListener(onProgressUpdateListener)
8}
9
10private val navigationListener = object : NavigationFragment.NavigationListener {
11 override fun onStarted() {
12 tomTomMap.addOnCameraChangeListener(onCameraChangeListener)
13 tomTomMap.changeCameraTrackingMode(CameraTrackingMode.FOLLOW_ROUTE)
14 tomTomMap.enableLocationMarker(LocationMarkerOptions(LocationMarkerOptions.Type.CHEVRON))
15 setSimulationLocationProviderToNavigation()
16 setMapMatchedLocationProvider()
17 setMapNavigationPadding()
18 }
19
20 override fun onFailed(error: NavigationError) {
21 Toast.makeText(this@MainActivity, error.message, Toast.LENGTH_SHORT).show()
22 stopNavigation()
23 }
24
25 override fun onStopped() {
26 stopNavigation()
27 }
28}
29
30private val onProgressUpdateListener = OnProgressUpdateListener {
31 tomTomMap.routes.first().progress = it.distanceAlongRoute
32}

Handle the updates to the navigation states using the NavigationListener:

  • Use onCameraChangeListener to observe camera tracking mode and detect if the camera is locked on the chevron. If the user starts to move the camera, it will change and you can adjust the UI to suit.
    1private val onCameraChangeListener by lazy {
    2 OnCameraChangeListener {
    3 val cameraTrackingMode = tomTomMap.cameraTrackingMode()
    4 if (cameraTrackingMode == CameraTrackingMode.FOLLOW_ROUTE) {
    5 navigationFragment.navigationView.showSpeedView()
    6 } else {
    7 navigationFragment.navigationView.hideSpeedView()
    8 }
    9 }
    10}
  • Use the SimulationLocationProvider for testing purposes.
    1private fun setSimulationLocationProviderToNavigation() {
    2 locationProvider = createSimulationLocationProvider(route!!)
    3 tomTomNavigation.locationProvider = locationProvider
    4 locationProvider.enable()
    5}
  • Once navigation is started, the camera is set to follow the user position, and the location indicator is changed to a chevron. To match raw location updates to the routes, use MapMatchedLocationProvider and set it to the TomTomMap.
    1private fun setMapMatchedLocationProvider() {
    2 val mapMatchedLocationProvider = MapMatchedLocationProvider(tomTomNavigation)
    3 tomTomMap.setLocationProvider(mapMatchedLocationProvider)
    4 mapMatchedLocationProvider.enable()
    5}
  • Set the bottom padding on the map. The padding sets a safe area of the MapView in which user interaction is not received. It is used to uncover the chevron in the navigation panel.
    1private fun setMapNavigationPadding() {
    2 val paddingBottom = resources.getDimensionPixelOffset(R.dimen.map_padding_bottom)
    3 val padding = Padding(0, 0, 0, paddingBottom)
    4 tomTomMap.setPadding(padding)
    5}
  • Stop the navigation process using NavigationFragment. This hides the UI elements and calls the TomTomNavigation.stop() method. Don’t forget to reset any map settings that were changed, such as camera tracking, location marker, and map padding.
    1private fun stopNavigation() {
    2 navigationFragment.stopNavigation()
    3 tomTomMap.changeCameraTrackingMode(CameraTrackingMode.NONE)
    4 tomTomMap.enableLocationMarker(LocationMarkerOptions(LocationMarkerOptions.Type.POINTER))
    5 resetMapPadding()
    6 navigationFragment.removeNavigationListener(navigationListener)
    7 tomTomNavigation.removeOnProgressUpdateListener(onProgressUpdateListener)
    8 clearMap()
    9 initLocationProvider()
    10 enableUserLocation()
    11}

Remember to close TomTomNavigation if it is no longer needed to release resources.

Run the application. You should see a globe showing the user’s location. Set the destination point with a long press. If you want to start navigation along the drawn route, tap it. Navigation should start with a guidance panel and voice instructions.

center

Next steps

The TomTom Navigation SDK allows you to customize the appearance of the map and its overlays, use your own Navigation UI components, or provide a custom implementation of certain navigation behaviors. See the following guides for more information: