Реалізація побудови маршрутів на карті мобільного додатку
Відображення маршруту на карті — це не просто намалювати лінію між двома точками. Потрібно отримати реальну геометрію доріг від routing API, відрисувати Polyline з потрібним стилем, показати дистанцію і час, обробити кілька варіантів маршруту і дати користувачу вибрати. Конкретна реалізація залежить від SDK і провайдера маршрутів.
Вибір провайдера: вбудований routing vs Directions API
| Провайдер | Платформа | Офлайн | Точність в РФ | Ціна |
|---|---|---|---|---|
| Google Directions API | iOS + Android | Ні | Добра | $5/1000 запитів |
| Apple MKDirections | iOS | Ні | Середня в РФ | Безплатно |
| Яндекс MapKit Router | iOS + Android | Так (Full SDK) | Відмінна в РФ | За тарифом |
| Mapbox Directions API | iOS + Android | Ні | Добра | Безплатний рівень |
| 2ГІС SDK Router | iOS + Android | Так | Добра в РФ | За тарифом |
| OSRM (self-hosted) | Будь-яка | Залежить | Залежить від даних | Безплатно |
Для більшості російських додатків із цінової чутливістю — Яндекс або 2ГІС з офлайн-режимом.
Google Maps: Directions API + відрисування Polyline
Google Maps SDK не містить вбудованого routing — потрібно викликати Directions REST API окремо і намалювати отриману геометрію вручну.
// Запит до 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=uk")
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) // з maps-utils
}
// Відрисування
fun drawRoute(googleMap: GoogleMap, points: List<LatLng>) {
googleMap.addPolyline(
PolylineOptions()
.addAll(points)
.color(Color.parseColor("#4285F4"))
.width(8f)
.geodesic(true)
.startCap(RoundCap())
.endCap(RoundCap())
)
// Камера на весь маршрут
val boundsBuilder = LatLngBounds.builder()
points.forEach { boundsBuilder.include(it) }
googleMap.animateCamera(
CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 100)
)
}
PolyUtil.decode із maps-utils розкодує Google Encoded Polyline. Без цієї утиліти довелось би писати decoder вручну — алгоритм простий, але навіщо.
Яндекс MapKit: DrivingRouter
Яндекс будує маршрути прямо в SDK без REST-вызовів:
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 // запросити кілька варіантів
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
}
// Клік по альтернативному маршруту
polyline.addTapListener { _, _ ->
selectRoute(index)
true
}
}
// Мета-інформація першого маршруту
routes.firstOrNull()?.let { route ->
val metadata = route.metadata.weight
val distance = metadata.distance.text // "350 км"
val time = metadata.timeWithTraffic.text // "4 ч 20 мин"
showRouteInfo(distance, time)
}
}
override fun onDrivingRoutesError(error: Error) {}
}
)
Пішохідні та транзитні маршрути
Замість DrivingRouter — PedestrianRouter або TransitRouter. Транзитний маршрут повертає список сегментів: пішені участки, автобуси, метро — з часом і зупинками.
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 }
// Рисуємо всі маршрути, перший — основний
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)
}
// Зум на маршрут
self?.mapView.setVisibleMapRect(
routes[0].polyline.boundingMapRect,
edgePadding: UIEdgeInsets(top: 60, left: 40, bottom: 80, right: 40),
animated: true
)
}
}
Проміжні точки (Waypoints)
Всі провайдери підтримують waypoints. У Google Directions API — параметр &waypoints=lat,lng|lat,lng. У Яндекс MapKit — додати RequestPoint з типом VIAPOINT між стартом і фінішем. У MKDirections — через MKDirections.Request.waypoints (доступні з iOS 16).
Типичні проблеми
Маршрут не перебудовується при зміні точок. Старий Polyline не видалено перед додаванням нового. Потрібно зберігати посилання на поточний overlay і видаляти його: mapView.removeOverlay(currentRoute).
Encoding polyline не декодується. Google використовує точність 1e5 (5 знаків після коми). Яндекс — власний формат. Не використовуйте універсальний decoder для Яндекс-маршрутів.
Строки
2–3 дні. Один тип маршруту без waypoints — 1 день. Кілька режимів транспорту, альтернативні маршрути, пересчёт — 2–3 дні. Вартість розраховується індивідуально.







