Manual map management

VERSION 1.25.6
PUBLIC PREVIEW

An offline NDS map is divided into map regions that can be updated individually. Updating can be done automatically, as described in the Offline map setup guide. For some use cases, you may require more control over which map regions are updated than what is automatically updated. In such cases, this SDK offers manual map management functionality.

The term manual comes from the common use case of a navigation application, where users are presented with a list of map regions, allowing them to manually select the regions they wish to update. However, manual map management can also be implemented based on your own logic and may not necessarily require user interaction.

This guide provides an overview of how the map structure is represented and explains the steps to perform map operations on the map regions.

Map structure

The regions of an offline NDS map are organized in a hierarchical tree structure. This structure is represented by the RegionGraph and CompositeRegionGraph, each offering a different perspective on the map structure.

  • The RegionGraph provides a detailed and fine-grained representation of the map structure. Only the lowest-level map regions can be updated.
  • The CompositeRegionGraph hides the lowest-level map regions and allows updating at any level in the tree.

You can see the visual differences between the two representations in the following images:

Note that the map structure often varies between different maps, based upon the map provider. NDS Maps provided by TomTom are divided in small map regions to limit data and storage usage when updating automatically. However, these small lower-level map regions are not meant to be shown to the end user. Therefore, TomTom recommends using the CompositeRegionGraph.

Fine-grained representation

The RegionGraph is a tree of RegionGraphNode instances. Each RegionGraphNode represents a map region and has a localized name and ID. Information about the lower-level nodes is provided in RegionGraphNodeState instances, such as:

  • Whether the region is currently installed
  • Whether updates are available for the region
  • The size of the update that can be downloaded.

Observing map structure and map region states

By implementing the RegionGraphListener, you can receive notifications whenever there are changes in the RegionGraph structure or the state of individual RegionGraphNode:

1private var rootNodes = listOf<RegionGraphNode>()
2private val nodeStates = mutableMapOf<RegionGraphNodeId, RegionGraphNodeState>()
3private val regionGraphListener =
4 object : RegionGraphListener {
5 override fun onRegionGraphChanged(
6 regionGraphResult: Result<RegionGraph, MapUpdateError>,
7 mapRegionStates: Map<RegionGraphNodeId, RegionGraphNodeState>,
8 ) {
9 regionGraphResult
10 .ifSuccess {
11 // Your code goes here
12 // For example, you can save the region graph and the states
13 rootNodes = it.roots
14 nodeStates.clear()
15 nodeStates.putAll(mapRegionStates)
16 }
17 .ifFailure {
18 // Your code goes here
19 }
20 }
21
22 override fun onMapRegionStatesChanged(mapRegionStates: Map<RegionGraphNodeId, RegionGraphNodeState>) {
23 // Your code goes here
24 // for example, update the current states
25 nodeStates.putAll(mapRegionStates)
26 }
27 }

When a listener is first added, it will be called with the current map structure and map region states.

After instantiating the NdsStoreUpdater, you can register the listener you created. To construct an NdsStoreUpdater object, refer to the Offline map quickstart.

ndsStoreUpdater.addRegionGraphListener(regionGraphListener)

Performing map operations

The SDK provides an API that allows you to perform map operations such as installing/updating, and uninstalling map regions.

Note that only the lowest-level nodes present in the RegionGraph support these map operations. For each RegionGraphNode, you can check the isUpdatableRegion property to identify if the node is eligible for map operations.

Each RegionGraphNode is identified by a RegionGraphNodeId. For each updatable RegionGraphNode, you can perform map operations via the scheduleMapOperations API. Here is an example:

1val regionsToUpdate = rootNodes.first().children.orEmpty()
2val regionsToUninstall = rootNodes.last().children.orEmpty()
3val mapOperations = mutableListOf<MapOperation>()
4
5regionsToUpdate.forEach {
6 if (it.isUpdatableRegion) { // make sure this node supports map operations
7 mapOperations.add(MapOperation(MapOperationType.InstallAndUpdate, it.id))
8 }
9}
10regionsToUninstall.forEach {
11 if (it.isUpdatableRegion) { // make sure this node supports map operations
12 mapOperations.add(MapOperation(MapOperationType.Uninstall, it.id))
13 }
14}
15ndsStoreUpdater.scheduleMapOperations(mapOperations)

NOTE: If automatic map updates are also in progress, the scheduled map operations take priority over those initiated by automatic map updates.

Canceling map operations

If you want to cancel one or more map operations, you can do that via the cancelAllMapOperations API. Here is an example:

val toBeCanceled = rootNodes.first().children?.map { it.id }.orEmpty()
ndsStoreUpdater.cancelAllMapOperations(toBeCanceled)

This cancels scheduled map operations, as well as automatic map updates.

Composite representation

The CompositeRegionGraph is a tree of CompositeRegion nodes. Each node represents a map region and has a localized name and ID. Information about each node is provided in CompositeRegionState instances, such as:

  • Whether the region is currently installed
  • Whether updates are available for the region
  • The size of the update that can be downloaded.

Observing map structure and map region states

By implementing the CompositeRegionListener, you can observe changes in the CompositeRegionGraph structure and the state of individual CompositeRegion nodes:

1private var rootCompositeRegions = setOf<CompositeRegion>()
2private val compositeRegionStates = mutableMapOf<CompositeRegionId, CompositeRegionState>()
3private val compositeRegionListener =
4 object : CompositeRegionListener {
5 override fun onCompositeRegionGraphChanged(
6 graphResult: Result<CompositeRegionGraph, MapUpdateError>,
7 changedStates: CompositeRegionStatesData?,
8 ) {
9 graphResult
10 .ifSuccess {
11 // Your code goes here
12 // For example, you can save the graph and the states
13 rootCompositeRegions = it.roots
14 compositeRegionStates.clear()
15 if (changedStates != null) {
16 compositeRegionStates.putAll(changedStates.stateMap)
17 }
18 }
19 .ifFailure {
20 // Your code goes here
21 }
22 }
23
24 override fun onCompositeRegionStatesChanged(changedStates: CompositeRegionStatesData) {
25 // for example, update the current states
26 compositeRegionStates.putAll(changedStates.stateMap)
27 }
28 }

When a listener is first added, it is called with the current map structure and map region states.

When opting for this composite representation, you need to initiate the CompositeRegionsUpdater object first. The listener can be added to that object:

val compositeRegionsUpdater = CompositeRegionsUpdater(ndsStoreUpdater)
compositeRegionsUpdater.addCompositeRegionListener(compositeRegionListener)

Performing map operations

The SDK provides an API that allows you to perform map operations such as installing/updating, and uninstalling map regions. When using CompositeRegionsUpdater, you can perform operations on any node in the map structure tree.

Each CompositeRegionGraph is identified with a CompositeRegionId. For each CompositeRegion, you can perform map operations via the scheduleMapOperations API of the CompositeRegionsUpdater object. Here is an example:

1// perform install and update on all regions
2val mapOperations =
3 rootCompositeRegions.map {
4 CompositeRegionOperation(it.id, MapOperationType.InstallAndUpdate)
5 }
6compositeRegionsUpdater.scheduleMapOperations(mapOperations)

NOTE: If automatic map updates are also in progress, the scheduled map operations take priority over those initiated by automatic map updates.

Canceling map operations

If you want to cancel one or more scheduled map operations, you can do that via the cancelAllMapOperations API of the CompositeRegionsUpdater.

UI suggestions for showing the map structure and map region states

Now that you have the data of map structure and the map region states, here are some suggestions for a user-interface for it. To represent the map structure and map region states, you can choose a suitable view framework such as ListView, RecycleViews, or any other appropriate framework. These frameworks provide efficient ways to display and manage lists or collections of data.

For instance, to display the map region name and the map region state, here is a layout with TextView:

1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:tools="http://schemas.android.com/tools"
3 android:layout_width="match_parent"
4 android:layout_height="match_parent" >
5
6 <TextView
7 android:id="@+id/regionName"
8 android:layout_width="wrap_content"
9 android:layout_height="wrap_content" />
10
11 <TextView
12 android:id="@+id/installState"
13 android:layout_width="wrap_content"
14 android:layout_height="wrap_content" />
15
16</LinearLayout>

Then here is the code snippets of assigning the data to the TextView:

1val node = rootCompositeRegions.first()
2val regionName = findViewById<TextView>(R.id.regionName)
3regionName.text = node.name
4
5val regionState = compositeRegionStates[node.id]
6val installState = findViewById<TextView>(R.id.installState)
7installState.text =
8 when (regionState?.installState) {
9 InstallState.NotInstalled -> "Not Installed"
10 InstallState.CompletelyInstalled -> "Completely Installed"
11 InstallState.PartiallyInstalled -> "Partially Installed"
12 InstallState.Inconsistent -> "Inconsistent"
13 else -> ""
14 }

Next steps

Since you have learned about manual map management, here are recommendations for the next steps: