Wayfinding
Using Mappedin SDK for Android with your own map requires a Pro license. Try a demo map for free or refer to the Pricing page for more information.
Wayfinding is the process of navigating through environments using spatial cues and instructions to efficiently reach a destination. Mappedin SDKs provide the tools needed to guide users from one location to another. It allows drawing a path between points on the map as well as creation of textual turn-by-turn directions for navigation.

Getting Directions
Directions form the core of wayfinding. Mappedin has path finding capabilities that allow it to generate directions from one target to another. These directions are used for drawing paths and providing the user with textual turn-by-turn directions.
Generate directions by calling MapData.getDirections() and pass in targets for the origin and destination. These targets are wrapped in a NavigationTarget, which can contain various types of targets.
MapData.getDirections() can accept a single NavigationTarget or an array of them for the origin and destination. If an array is used, Mappedin will choose the targets that are closest to each other. An example of where this could be used is when a user asks for directions to a washroom. There may be many Spaces named Washroom. The app can pass all washroom spaces to getDirections and receive directions to the nearest one.
Directions for some maps may appear jagged or not smooth. This is due to the SDK attempting to find the shortest path through the map's geometry. Directions can be smoothed by setting GetDirectionsOptions.smoothing to enabled in the GetDirectionsOptions passed to MapData.getDirections(). Directions are smoothed by default for maps created using Mappedin Maker and disabled for maps created using Mappedin CMS.
Drawing Navigation
When a user needs to get from point A to point B, drawing a path on the map helps them to navigate to their destination. It can help them to visualize the route they'll need to take, like a good treasure map.
Navigation is a helper class to display wayfinding easily on the map. Functionality of Navigation could be replicated by drawing the paths using Paths and adding well designed markers at connection points.
Note that the MapView class instantiates the Navigation class and exposes it as MapView.navigation. Use MapView.navigation to utilize Navigation's methods.
Navigation.draw() allows for easily drawing multiple components that make up a wayfinding illustration. It shows a human figure to mark the start point, a path with animated directional arrows, pulses in the direction of travel and a pin to mark the destination. Each of these components can be customized to match an app's style.
The following sample uses the default navigation settings to navigate from the Cafeteria to the Gymnasium.
// Draw an interactive navigation path from Cafeteria to Gymnasium
mapView.mapData.getByType<Space>(MapDataType.SPACE) { result ->
result.onSuccess { locations ->
val cafeteria = locations.find { it.name == "Cafeteria" }
val gymnasium = locations.find { it.name == "Gymnasium" }
if (cafeteria != null && gymnasium != null) {
mapView.mapData.getDirections(
NavigationTarget.SpaceTarget(cafeteria),
NavigationTarget.SpaceTarget(gymnasium),
) { dirResult ->
dirResult.onSuccess { directions ->
if (directions != null) {
mapView.navigation.draw(directions) { }
}
}
}
}
}
}
The navigation visualization can be customized by passing in a NavigationOptions object to Navigation.draw(). The following example demonstrates how to customize the navigation visualization, using the pathOptions, markerOptions and createMarkers properties.
A complete example demonstrating custom navigation markers and paths can be found in the Mappedin Android Github repo: NavigationDemoActivity.kt
private fun drawNavigation() {
val pathOptions =
com.mappedin.models.AddPathOptions(
color = "#4b90e2",
displayArrowsOnPath = true,
animateDrawing = true,
)
NavigationOptions(
pathOptions = pathOptions,
createMarkers =
NavigationOptions.CreateMarkers.withCustomMarkers(
departure =
NavigationOptions.CreateMarkers.CreateMarkerValue.CustomMarker(
template = getDepartureMarker(),
options =
AddMarkerOptions(
rank = AddMarkerOptions.Rank.Tier(CollisionRankingTier.ALWAYS_VISIBLE),
interactive = AddMarkerOptions.Interactive.True,
),
),
destination =
NavigationOptions.CreateMarkers.CreateMarkerValue.CustomMarker(
template = getDestinationMarker(),
options =
AddMarkerOptions(
rank = AddMarkerOptions.Rank.Tier(CollisionRankingTier.ALWAYS_VISIBLE),
interactive = AddMarkerOptions.Interactive.True,
),
),
connection =
NavigationOptions.CreateMarkers.CreateMarkerValue.CustomMarker(
template = getConnectionMarker(),
options =
AddMarkerOptions(
rank = AddMarkerOptions.Rank.Tier(CollisionRankingTier.ALWAYS_VISIBLE),
interactive = AddMarkerOptions.Interactive.True,
),
),
),
)
mapView.navigation.draw(directions, navOptions) { }
}
// Functions to get the SVGs for the markers
private fun getDepartureMarker(): String =
"""
<svg width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
<!-- Pirate Ship -->
<circle cx="24" cy="24" r="22" fill="#8B4513" opacity="0.2"/>
<path d="M12 28 L12 22 L18 18 L30 18 L36 22 L36 28 L32 32 L16 32 Z" fill="#8B4513" stroke="#5D2E0F" stroke-width="2"/>
<rect x="22" y="10" width="4" height="12" fill="#5D2E0F"/>
<path d="M26 10 L36 14 L26 18" fill="#DC143C"/>
<circle cx="24" cy="24" r="3" fill="#FFD700"/>
</svg>
""".trimIndent()
private fun getDestinationMarker(): String =
"""
<svg width="56" height="56" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg">
<!-- Background glow -->
<circle cx="28" cy="28" r="26" fill="#FFD700" opacity="0.2"/>
<!-- Treasure chest body -->
<rect x="14" y="24" width="28" height="18" rx="2" fill="#8B4513" stroke="#5D2E0F" stroke-width="2"/>
<!-- Chest lid -->
<path d="M 14 24 Q 14 18 20 16 L 36 16 Q 42 18 42 24" fill="#6D3913" stroke="#5D2E0F" stroke-width="2"/>
<!-- Lid highlight -->
<path d="M 16 24 Q 16 20 20 18 L 36 18 Q 40 20 40 24" fill="#8B4513"/>
<!-- Front band -->
<rect x="14" y="24" width="28" height="5" fill="#5D2E0F"/>
<!-- Center lock plate -->
<rect x="26" y="24" width="4" height="18" fill="#5D2E0F"/>
<!-- Lock -->
<circle cx="28" cy="33" r="3" fill="#DAA520" stroke="#8B6914" stroke-width="1"/>
<circle cx="28" cy="33" r="1.5" fill="#5D2E0F"/>
<!-- Gold coins spilling out -->
<circle cx="20" cy="30" r="2.5" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
<circle cx="36" cy="30" r="2.5" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
<circle cx="18" cy="36" r="2" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
<circle cx="38" cy="36" r="2" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
<circle cx="24" cy="38" r="2" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
<circle cx="32" cy="38" r="2" fill="#FFD700" stroke="#DAA520" stroke-width="1"/>
</svg>
""".trimIndent()
private fun getConnectionMarker(): String =
"""
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<!-- Compass -->
<circle cx="20" cy="20" r="18" fill="#2C3E50" opacity="0.2"/>
<circle cx="20" cy="20" r="14" fill="#34495E" stroke="#95A5A6" stroke-width="2"/>
<circle cx="20" cy="20" r="10" fill="#2C3E50"/>
<path d="M20 12 L23 20 L20 22 L17 20 Z" fill="#DC143C"/>
<path d="M20 28 L23 20 L20 18 L17 20 Z" fill="#ECF0F1"/>
<circle cx="20" cy="20" r="2" fill="#FFD700"/>
</svg>
""".trimIndent()
Highlighting a Portion of a Path
The path drawn by Navigation or Paths can have a portion highlighted to draw attention to it. This could be used to show the progress of a user's journey. To highlight a portion of the path, use the Navigation.highlightPathSection or Paths.highlightSection methods.
The following code demonstrates how to highlight a portion of a path, starting from the origin of directions and ending at a given coordinate. Note that the floor must be provided when using a coordinate to highlight a path section.
val coordinate = mapView.createCoordinate(
position.latitude.toDouble(),
position.longitude.toDouble(),
position.floorOrFloorId
)
directions?.coordinates?.firstOrNull()?.let { firstCoordinate ->
// Highlight the traveled path.
mapView.Navigation.highlightPathSection(
from = firstCoordinate,
to = coordinate,
options = JSONObject().apply {
put("animationDuration", 0)
put("color", "#9d9d9d")
put("widthMultiplier", 1.1)
}
)
}
Navigating From a Click Event
A navigation start or end point may originate from a user clicking on the map. It is possible to create a start and or end point using the click event and accessing its Events payload.
A complete example demonstrating Navigation from a click event can be found in the Mappedin Android Github repo: PathsDemoActivity.kt
The code shown below uses the first click position as a start point and the second click position as an end point to create directions between them.
// Set all spaces to be interactive.
mapView.mapData.getByType<Space>(MapDataType.SPACE) { result ->
result.onSuccess { spaces ->
spaces.forEach { space ->
mapView.updateState(space, GeometryUpdateState(interactive = true)) { }
}
// Handle click events
mapView.on(Events.Click) { clickPayload ->
clickPayload ?: return@on
val spaces = clickPayload.spaces
if (spaces == null || spaces.isEmpty()) {
// Click on non-space area when path exists - reset
if (path != null) {
mapView.paths.removeAll()
startSpace = null
path = null
setSpacesInteractive(mapView, true)
instructionText.text = "1. Click on a space to select it as the starting point."
}
return@on
}
val clickedSpace = spaces[0]
when {
startSpace == null -> {
// Step 1: Select starting space
startSpace = clickedSpace
instructionText.text = "2. Click on another space to select it as the end point."
}
path == null -> {
// Step 2: Select ending space and create path
val start = startSpace ?: return@on
mapView.mapData.getDirections(
NavigationTarget.SpaceTarget(start),
NavigationTarget.SpaceTarget(clickedSpace),
) { result ->
result.onSuccess { directions ->
if (directions != null) {
mapView.navigation.draw(directions.coordinates) { pathResult ->
pathResult.onSuccess { createdPath ->
path = createdPath
setSpacesInteractive(mapView, false)
instructionText.text = "3. Click anywhere to remove the path."
}
}
}
}
}
}
else -> {
// Step 3: Remove path and reset
mapView.paths.removeAll()
startSpace = null
path = null
setSpacesInteractive(mapView, true)
instructionText.text = "1. Click on a space to select it as the starting point."
}
}
}
}
}
Multi-Floor Wayfinding
Using Navigation, no additional work is needed to provide wayfinding between floors. Whenever a user needs to switch a floor, an interactive tooltip with an icon indicating the type of Connection (such as an elevator or stairs) will be drawn. By clicking or tapping the tooltip, the map view switches to the destination floor.
Multi-Destination Wayfinding
Multi-segment directions are directions that include multiple legs. They can be created by passing an array of destinations to MapData.getDirectionsMultiDestination().
val start = // ... get entrance space
val destinations = listOf<Any>(
NavigationTarget.SpaceTarget(store1),
NavigationTarget.SpaceTarget(store2),
NavigationTarget.SpaceTarget(restaurant)
)
mapData.getDirectionsMultiDestination(
NavigationTarget.SpaceTarget(start),
destinations
) { result ->
result.onSuccess { allDirections ->
allDirections?.forEachIndexed { index, directions ->
Log.d("MapData", "Route ${index + 1}: ${directions.distance}m")
}
}
}
Wayfinding Using Accessible Routes
When requesting directions, it is important to consider the needs of the user. Users with mobility restrictions may require a route that avoids stairs and escalators and instead uses ramps or elevators.
The getDirections method accepts GetDirectionsOptions, which allows specifying whether an accessible route should be returned. By default, the shortest available route is chosen. The following code demonstrates how to request directions that make use of accessible routes.
mapData.getDirections(
NavigationTarget.SpaceTarget(space1),
NavigationTarget.SpaceTarget(space2),
GetDirectionsOptions(accessible = true)
) { result ->
result.onSuccess { directions ->
// Use directions
}
}
Drawing a Path
A complete example demonstrating drawing a path can be found in the Mappedin Android Github repo: PathsDemoActivity.kt
While Navigation provides a complete start and end navigation illustration, it may be desired to draw just the path. This can be done using Paths.
Note that the MapView class implements the Paths interface and exposes it as MapView.paths. Use MapView.paths to utilize Paths methods.
Paths can be drawn from one coordinate to another using Paths.add(). If using just two coordinates, the path will be drawn straight between the two points. This may work for some scenarios, but in most cases an app will need to show the user their walking path, going through doors and avoiding walls and other objects. Such a path of coordinates can be created by calling the MapData.getDirections() method, passing in a start and end NavigationTarget. Note that a Space requires an entrance to be used as a target.
The width of the path is set using the width parameter. This value is in meters. Additional path styles are outlined later in this guide in the Path Styles section.
mapView.mapData.getDirections(
NavigationTarget.SpaceTarget(start),
NavigationTarget.SpaceTarget(end),
) { result ->
result.onSuccess { directions ->
if (directions != null) {
val opts =
AddPathOptions(
width = Width.Value(1.0)
)
mapView.paths.add(directions.coordinates, opts) { pathResult ->
pathResult.onSuccess { createdPath ->
// Store the path for later use.
path = createdPath
}
}
}
}
}
Removing Paths
There are two methods that can be used to remove paths. Paths.remove() accepts a path to remove and is used to remove a single path. To remove all paths, Paths.removeAll() can be used.
// Remove a single path.
mapView.paths.remove(path);
// Remove all paths
mapView.paths.removeAll();
Path Styles
Mappedin SDKs offer many options to customise how paths appear to the user. Path animations, color, width and many more options can be set using AddPathOptions.
Dynamic Routing
When generating directions, Mappedin SDKs provide the most direct route possible between the origin and destination. There can be scenarios when this is not desired, such as when there there is an obstruction like a spill that requires cleaning or where maintenance crews are active.
A complete example demonstrating dynamic routing can be found in the Mappedin Android Github repo: AreaShapesDemoActivity.kt
The MapData.getDirections() method accepts a zones parameter that denotes areas that could be avoided. Zones are defined using DirectionZone and contain a cost, floor and geometry.
cost represents the additional cost for navigation through the zone and can range from from 0 to Infinity. A cost of 0 will make the zone free to navigate through. A zone cost of Infinity will block passage through the zone. Multiple zones occupying the same location they are added together, along with the cost from the map. The SDK may route directions through a zone if all other routes have a higher cost.
floor represents the floor for the zone. If not specified the zone blocks all floors.
geometry is held within a Feature that contains a Geometry object.
Turn-by-Turn Directions
Turn-by-Turn directions are a set of text instructions describing the route the user needs to take. An example is "Turn right and go 10 meters". These could be shown to the user in a list to give them an overview with all steps they need to take, or the current instruction could be displayed along with a map showing the next leg of their journey.
A complete example demonstrating turn-by-turn directions can be found in the Mappedin Android Github repo: TurnByTurnDemoActivity.kt
The code sample assembles these actions together and:
- Gets directions between the start and end NavigationTarget.
- Draws a path using the directions' coordinates.
- Creates a Marker with textual turn-by-turn instructions for each leg in the journey. Refer to the Marker Guide for more information on using Markers.
private var currentDirections: Directions? = null
private fun onMapReady() {
// Add labels to all named spaces
mapView.mapData.getByType<Space>(MapDataType.SPACE) { spacesResult ->
spacesResult.onSuccess { spaces ->
spaces.forEach { space ->
if (space.name.isNotEmpty()) {
mapView.labels.add(
target = space,
text = space.name,
options = AddLabelOptions(interactive = true),
) { }
}
}
// Find destination space
val destination = spaces.find { it.name == "Family Med Lab EN-09" }
// Get origin object
mapView.mapData.getByType<MapObject>(MapDataType.MAP_OBJECT) { objResult ->
objResult.onSuccess { objects ->
val origin = objects.find { it.name == "Lobby" }
if (origin != null && destination != null) {
getAndDisplayDirections(origin, destination)
}
}
}
}
}
}
private fun getAndDisplayDirections(
origin: MapObject,
destination: Space,
) {
mapView.mapData.getDirections(
NavigationTarget.MapObjectTarget(origin),
NavigationTarget.SpaceTarget(destination),
) { result ->
result.onSuccess { directions ->
if (directions != null) {
currentDirections = directions
// Focus on the first 3 steps in the journey
val focusCoordinates =
directions.coordinates.take(3).map {
FocusTarget.CoordinateTarget(it)
}
val focusOptions =
FocusOnOptions(
screenOffsets =
InsetPadding(
top = 50.0,
left = 50.0,
bottom = 50.0,
right = 50.0,
),
)
mapView.camera.focusOn(focusCoordinates, focusOptions)
// Add markers for each direction instruction
addInstructionMarkers(directions)
// Draw navigation by default
drawNavigation()
}
}
}
}
private fun drawNavigation() {
val directions = currentDirections ?: return
mapView.navigation.draw(directions) { }
}
private fun addInstructionMarkers(directions: Directions) {
val instructions = directions.instructions
for (i in instructions.indices) {
val instruction = instructions[i]
val nextInstruction = if (i < instructions.size - 1) instructions[i + 1] else null
val isLastInstruction = i == instructions.size - 1
val markerText =
if (isLastInstruction) {
"You Arrived!"
} else {
val actionType = instruction.action.type
val bearing = instruction.action.bearing ?: ""
val distance = nextInstruction?.distance?.roundToInt() ?: 0
"$actionType $bearing and go $distance meters"
}
val markerTemplate =
"""
<div style="
background: white;
padding: 8px 12px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 12px;
white-space: nowrap;
">
<p style="margin: 0;">$markerText</p>
</div>
""".trimIndent()
mapView.markers.add(
instruction.coordinate,
markerTemplate,
AddMarkerOptions(
rank = AddMarkerOptions.Rank.Tier(CollisionRankingTier.ALWAYS_VISIBLE),
),
) { }
}
}