Manual map management
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 regionGraphResult10 .ifSuccess {11 // Your code goes here12 // For example, you can save the region graph and the states13 rootNodes = it.roots14 nodeStates.clear()15 nodeStates.putAll(mapRegionStates)16 }17 .ifFailure {18 // Your code goes here19 }20 }2122 override fun onMapRegionStatesChanged(mapRegionStates: Map<RegionGraphNodeId, RegionGraphNodeState>) {23 // Your code goes here24 // for example, update the current states25 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>()45regionsToUpdate.forEach {6 if (it.isUpdatableRegion) { // make sure this node supports map operations7 mapOperations.add(MapOperation(MapOperationType.InstallAndUpdate, it.id))8 }9}10regionsToUninstall.forEach {11 if (it.isUpdatableRegion) { // make sure this node supports map operations12 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 graphResult10 .ifSuccess {11 // Your code goes here12 // For example, you can save the graph and the states13 rootCompositeRegions = it.roots14 compositeRegionStates.clear()15 if (changedStates != null) {16 compositeRegionStates.putAll(changedStates.stateMap)17 }18 }19 .ifFailure {20 // Your code goes here21 }22 }2324 override fun onCompositeRegionStatesChanged(changedStates: CompositeRegionStatesData) {25 // for example, update the current states26 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 regions2val 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" >56 <TextView7 android:id="@+id/regionName"8 android:layout_width="wrap_content"9 android:layout_height="wrap_content" />1011 <TextView12 android:id="@+id/installState"13 android:layout_width="wrap_content"14 android:layout_height="wrap_content" />1516</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.name45val 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: