Skip to main content

Fly To Traffic

Last updated: June 19, 2026 | 4 minutes read

This example demonstrates how to calculate a route and fly the camera to a traffic event along it. A route is computed between two waypoints (London and Paris), presented on the map, and the camera is then animated to centre on the first traffic event found on the route. The map remains fully interactive, supporting pan, pinch-zoom, rotate and tilt.

The route presented on the map with the camera centered on a traffic event

Calculating the Route

Once the worldwide road map is confirmed up to date, the callback is cleared so it fires only once, then a route is calculated between two Landmark waypoints with calculateRoute.

calculateRoute returns an error code synchronously, indicating whether the calculation could be started. A non-NoError value means it never started, so the onCompleted callback won't fire - the error is therefore reported here with an error dialog.

MainActivity.ktView on Github
SdkSettings.onWorldwideRoadMapSupportStatus = { status ->
if (status == EOffboardListenerStatus.UpToDate) {
// Clear the listener immediately to avoid repeated route calculations.
SdkSettings.onWorldwideRoadMapSupportStatus = {}

SdkCall.runSynced {
val waypoints = arrayListOf(
Landmark("London", 51.5073204, -0.1276475),
Landmark("Paris", 48.8566932, 2.3514616),
)

// calculateRoute returns synchronously whether the calculation could be
// started. On failure onCompleted never fires, so report the error here.
val errorCode = routingService.calculateRoute(waypoints)
if (errorCode != GemError.NoError) {
val errorMessage = GemError.getMessage(errorCode, this)
runOnAliveUi { showDialog(getString(R.string.routing_failed_to_start, errorMessage)) }
}
}
}
}

Retrieving Traffic Events and Flying to One

When routing completes, the first route is taken and its trafficEvents are read. If the route has no traffic events, an error dialog is shown; otherwise the route is presented on the map without auto-centering and the first traffic event is passed to flyToTraffic.

MainActivity.ktView on Github
private val routingService = RoutingService(
onStarted = {
binding.progressBar.visibility = View.VISIBLE
},

onCompleted = onCompleted@{ routes, gemError, _ ->
binding.progressBar.visibility = View.GONE

when (gemError) {
GemError.NoError -> {
if (routes.isEmpty()) return@onCompleted

val route = routes[0]

SdkCall.runSynced {
// Retrieve traffic events from the main route.
val events = route.trafficEvents

if (events.isNullOrEmpty()) {
runOnAliveUi { showDialog(getString(R.string.no_traffic_events)) }
return@runSynced
}

// Display the route on the map without auto-centering.
binding.gemSurfaceView.mapView?.presentRoute(route, centerMapView = false)

flyToTraffic(events[0])
}
}
else -> {
val errorMessage = SdkCall.runSynced { GemError.getMessage(gemError, this) }
runOnAliveUi { showDialog(getString(R.string.routing_error, errorMessage)) }
}
}
},
)

Centering on the Traffic Event

flyToTraffic animates the camera to the chosen traffic event with centerOnRouteTrafficEvent, framing it inside the free-space rectangle so it is not hidden behind the toolbar or the system bars.

MainActivity.ktView on Github
// Centers the camera on the given traffic event with a smooth animation.
private fun flyToTraffic(trafficEvent: RouteTrafficEvent) = SdkCall.runSynced {
binding.gemSurfaceView.mapView?.centerOnRouteTrafficEvent(
trafficEvent,
rc = getFreeSpaceRect(),
animation = Animation(EAnimation.Linear, 900),
viewAngle = 0.0,
)
}

getFreeSpaceRect() builds the rectangle the camera is framed within, from the toolbar position, the window insets and a fixed margin.

MainActivity.ktView on Github
// Returns the usable screen area excluding system bars and the toolbar.
private fun getFreeSpaceRect(): Rect {
val root = binding.root
val insets = ViewCompat.getRootWindowInsets(root)?.getInsets(SYSTEM_INSET_TYPES)

val width = root.width.takeIf { it > 0 } ?: resources.displayMetrics.widthPixels
val height = root.height.takeIf { it > 0 } ?: resources.displayMetrics.heightPixels

val left = (insets?.left ?: 0) + inflate
val right = (width - (insets?.right ?: 0) - inflate).coerceAtLeast(left)

val topInset = (insets?.top ?: 0) + inflate
val toolbarBottom = (binding.toolbar.bottom.takeIf { it > 0 } ?: 0) + inflate
val top = maxOf(topInset, toolbarBottom)
val bottom = (height - (insets?.bottom ?: 0) - inflate).coerceAtLeast(top)

return Rect(left, top, right, bottom)
}