Implementation of Route Building on a Map in a Mobile Application
Displaying a route on a map is not just drawing a line between two points. You need to get the real geometry of roads from a routing API, render Polyline with the desired style, show distance and time, handle multiple route variants, and let the user choose. The specific implementation depends on the SDK and route provider.
Provider Choice: Built-in Routing vs Directions API
| Provider | Platform | Offline | Accuracy in RF | Price |
|---|---|---|---|---|
| Google Directions API | iOS + Android | No | Good | $5/1000 requests |
| Apple MKDirections | iOS | No | Medium in RF | Free |
| Yandex MapKit Router | iOS + Android | Yes (Full SDK) | Excellent in RF | Per tariff |
| Mapbox Directions API | iOS + Android | No | Good | Free tier |
| 2GIS SDK Router | iOS + Android | Yes | Good in RF | Per tariff |
| OSRM (self-hosted) | Any | Depends | Depends on data | Free |
For most Russian applications with price sensitivity — Yandex or 2GIS with offline mode.
Google Maps: Directions API + Polyline Drawing
Google Maps SDK does not contain built-in routing — you need to call the Directions REST API separately and draw the obtained geometry manually.
// Request to Directions API
suspend fun getDirections(
origin: LatLng,
destination: LatLng
): List<LatLng> {
val url = buildString {
append("https://maps.googleapis.com/maps/api/directions/json")
append("?origin=${origin.latitude},${origin.longitude}")
append("&destination=${destination.latitude},${destination.longitude}")
append("&mode=driving")
append("&language=en")
append("&key=$MAPS_API_KEY")
}
val response = httpClient.get(url)
val json = JSONObject(response.body<String>())
val route = json.getJSONArray("routes").getJSONObject(0)
val overviewPolyline = route.getJSONObject("overview_polyline").getString("points")
return PolyUtil.decode(overviewPolyline) // from maps-utils
}
// Drawing
fun drawRoute(googleMap: GoogleMap, points: List<LatLng>) {
googleMap.addPolyline(
PolylineOptions()
.addAll(points)
.color(Color.parseColor("#4285F4"))
.width(8f)
.geodesic(true)
.startCap(RoundCap())
.endCap(RoundCap())
)
// Camera on entire route
val boundsBuilder = LatLngBounds.builder()
points.forEach { boundsBuilder.include(it) }
googleMap.animateCamera(
CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 100)
)
}
PolyUtil.decode from maps-utils decodes Google Encoded Polyline. Without this utility, you would have to write a decoder manually — the algorithm is simple, but why.
Yandex MapKit: DrivingRouter
Yandex builds routes directly in the SDK without REST calls:
val drivingRouter = DirectionsFactory.getInstance()
.createDrivingRouter(DrivingRouterType.COMBINED)
val routePoints = listOf(
RequestPoint(Point(55.7558, 37.6173), RequestPointType.WAYPOINT, null, null),
RequestPoint(Point(59.9343, 30.3351), RequestPointType.WAYPOINT, null, null)
)
val drivingSession = drivingRouter.requestRoutes(
routePoints,
DrivingOptions().apply {
routesCount = 3 // request several variants
avoidTolls = false
avoidPoorConditions = true
},
VehicleOptions(),
object : DrivingSession.DrivingRouteListener {
override fun onDrivingRoutes(routes: List<DrivingRoute>) {
routes.forEachIndexed { index, route ->
val color = if (index == 0) Color.BLUE else Color.GRAY
val polyline = mapObjectCollection.addPolyline(route.geometry).apply {
strokeColor = color
strokeWidth = if (index == 0) 6f else 3f
zIndex = if (index == 0) 1f else 0f
}
// Tap on alternative route
polyline.addTapListener { _, _ ->
selectRoute(index)
true
}
}
// Meta information of first route
routes.firstOrNull()?.let { route ->
val metadata = route.metadata.weight
val distance = metadata.distance.text // "350 km"
val time = metadata.timeWithTraffic.text // "4 h 20 min"
showRouteInfo(distance, time)
}
}
override fun onDrivingRoutesError(error: Error) {}
}
)
Walking and Transit Routes
Instead of DrivingRouter — use PedestrianRouter or TransitRouter. Transit route returns a list of segments: walking sections, buses, metro — with times and stops.
iOS MapKit: MKDirections
func buildDrivingRoute(from: CLLocationCoordinate2D, to: CLLocationCoordinate2D) {
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: from))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: to))
request.transportType = .automobile
request.requestsAlternateRoutes = true
MKDirections(request: request).calculate { [weak self] response, error in
guard let routes = response?.routes, !routes.isEmpty else { return }
// Draw all routes, first is primary
routes.enumerated().forEach { index, route in
let renderer = MKPolylineRenderer(polyline: route.polyline)
renderer.strokeColor = index == 0 ? .systemBlue : .systemGray
renderer.lineWidth = index == 0 ? 5 : 3
self?.mapView.addOverlay(route.polyline, level: .aboveRoads)
}
// Zoom to route
self?.mapView.setVisibleMapRect(
routes[0].polyline.boundingMapRect,
edgePadding: UIEdgeInsets(top: 60, left: 40, bottom: 80, right: 40),
animated: true
)
}
}
Intermediate Points (Waypoints)
All providers support waypoints. In Google Directions API — parameter &waypoints=lat,lng|lat,lng. In Yandex MapKit — add RequestPoint with type VIAPOINT between start and finish. In MKDirections — via MKDirections.Request.waypoints (available since iOS 16).
Common Problems
Route doesn't rebuild when points change. Old Polyline not removed before adding new one. You need to store a reference to the current overlay and remove it: mapView.removeOverlay(currentRoute).
Encoding polyline not decoded. Google uses precision 1e5 (5 decimal places). Yandex — custom format. Don't use universal decoder for Yandex routes.
Timeline
2–3 days. One route type without waypoints — 1 day. Multiple transport modes, alternative routes, recalculation — 2–3 days. Cost is calculated individually.







