Реализация построения маршрутов на карте мобильного приложения
Отображение маршрута на карте — это не просто нарисовать линию между двумя точками. Нужно получить реальные геометрию дорог от routing API, отрисовать Polyline с нужным стилем, показать дистанцию и время, обработать несколько вариантов маршрута и дать пользователю выбрать. Конкретная реализация зависит от SDK и провайдера маршрутов.
Выбор провайдера: встроенный routing vs Directions API
| Провайдер | Платформа | Офлайн | Точность в РФ | Цена |
|---|---|---|---|---|
| Google Directions API | iOS + Android | Нет | Хорошая | $5/1000 запросов |
| Apple MKDirections | iOS | Нет | Средняя в РФ | Бесплатно |
| Яндекс МапKit Router | iOS + Android | Есть (Full SDK) | Отличная в РФ | По тарифу |
| Mapbox Directions API | iOS + Android | Нет | Хорошая | Есть бесплатный tier |
| 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=ru")
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 дня. Стоимость рассчитывается индивидуально.







