Навигация
С помощью SDK вы можете работать со следующими сценариями навигации:
- Запустить навигатор в разных режимах (свободная навигация, ведение по маршруту и симуляция ведения), в том числе внутри зданий.
- Предлагать пользователю альтернативные маршруты по пути.
- Получать динамические данные маршрута в ходе навигации.
Чтобы создать навигатор, можно использовать готовый элемент интерфейса INavigationView и класс NavigationManager.
Для этого нужно добавить на карту маркер с текущим местоположением и создать слой навигатора с помощью фабрики INavigationViewFactory и класса NavigationManager.
// Создаём фабрику объектов карты.
guard let mapFactory = try? sdk.makeMapFactory(options: .default) else {
return
}
// Добавляем слой карты в иерархию представлений.
let mapView = mapFactory.mapView
mapView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(mapView)
NSLayoutConstraint.activate([
mapView.leftAnchor.constraint(equalTo: containerView.leftAnchor),
mapView.rightAnchor.constraint(equalTo: containerView.rightAnchor),
mapView.topAnchor.constraint(equalTo: containerView.topAnchor),
mapView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
// Добавляем на карту маркер с текущим местоположением.
let locationSource = MyLocationMapObjectSource(
context: sdk.context,
directionBehaviour: .followSatelliteHeading,
controller: createSmoothMyLocationController()
)
let map = mapFactory.map
map.addSource(source: locationSource)
// Создаём NavigationManager.
let navigationManager = NavigationManager(platformContext: sdk.context)
// Добавляем карту в навигатор.
navigationManager.mapManager.addMap(map: map)
// Создаём фабрику UI-компонентов навигатора.
let navigationViewFactory = sdk.makeNavigationViewFactory()
// Создаём с помощью фабрики слой навигатора и размещаем его в иерархии выше слоя карты.
let navigationView = navigationViewFactory.makeNavigationView(
map: map,
navigationManager: navigationManager
)
navigationView.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(navigationView)
NSLayoutConstraint.activate([
navigationView.leftAnchor.constraint(equalTo: containerView.leftAnchor),
navigationView.rightAnchor.constraint(equalTo: containerView.rightAnchor),
navigationView.topAnchor.constraint(equalTo: containerView.topAnchor),
navigationView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
// Добавляем обработчик нажатия кнопки закрытия.
navigationView.closeButtonCallback = { [weak navigationManager] in
navigationManager?.stop()
}
Навигатор может работать в трёх режимах: свободная навигация, ведение по маршруту и симуляция ведения.
Настройки навигатора можно изменить через свойства NavigationManager.
Свободная навигация
В этом режиме маршрут следования отсутствует, но навигатор будет информировать о превышениях скорости, дорожных камерах, авариях и ремонтных работах.
Чтобы запустить навигатор в этом режиме, нужно вызвать метод start()
без параметров.
navigationManager.start()
Ведение по маршруту
В этом режиме на карте будет построен маршрут от текущего местоположения до указанной точки назначения, и пользователь будет получать инструкции по мере движения.
Чтобы запустить навигатор в этом режиме, нужно вызвать метод start()
и указать объект RouteBuildOptions - координаты точки назначения и настройки маршрута.
let routeBuildOptions = RouteBuildOptions(
finishPoint: RouteSearchPoint(
coordinates: GeoPoint(
latitude: 55.752425,
longitude: 37.613983
)
),
routeSearchOptions: routeSearchOptions
)
navigationManager.start(routeBuildOptions)
Дополнительно при вызове метода start()
можно указать объект TrafficRoute — готовый маршрут для навигации (см. детали в разделе Маршруты). В таком случае навигатор не будет пытаться построить маршрут от текущего местоположения, а начнёт ведение по указанному маршруту.
// Ищем маршрут.
self.routeSearchCancellable = routesFuture.sink { routes in
guard let route = routes.first else { return }
// Настройки маршрута.
let routeBuildOptions = RouteBuildOptions(
finishPoint: finishPoint,
routeSearchOptions: routeSearchOptions
)
// Запускаем навигатор.
navigationManager.start(
routeBuildOptions: routeBuildOptions,
trafficRoute: route
)
} failure: { error in
print("Не удалось найти маршрут: \\(error)")
}
Симуляция ведения по маршруту
В этом режиме навигатор не будет отслеживать реальное местоположение устройства, а запустит симулированное движение по указанному маршруту. Режим удобно использовать для отладки.
Чтобы запустить навигатор в режиме симуляции, нужно вызвать метод startSimulation()
, указав готовый маршрут (TrafficRoute) и его настройки (RouteBuildOptions).
Скорость движения можно изменить с помощью свойства SimulationSettings.speed (метры в секунду).
navigationManager.simulationSettings.speed = 30 / 3.6
navigationManager.startSimulation(
routeBuildOptions: routeBuildOptions,
trafficRoute: route
)
Остановить симуляцию можно с помощью метода stop()
.
navigationManager.stop()
Навигация внутри зданий
SDK предоставляет возможность строить маршруты внутри некоторых зданий, для которых существуют этажные планы. Например, можно проложить маршрут до конкретного магазина в торговом центре, составить сложный маршрут между несколькими точками и тд.
Этот тип навигации включен в набор стандартной поставки Full SDK, однако имеет ряд ограничений:
- Может быть активирован только в режиме пешеходной навигации.
- Из-за особенностей работы современных систем позиционирования, возможности определения позиции внутри зданий сильно ограничены. Когда система определит, что устройство попало в пределы здания, слежение за геопозицей и ее отображение отключится, оставив построенный маршрут и рекомендации по проходу. Мы работаем над преодолением этих трудностей и надеемся предоставить надежный способ определения местоположения внутри зданий в будущем.
Создание маршрута
Создание маршрута происходит по принципам, описанным в разделе Ведение по маршруту с несколькими важными дополнениями. При создании конечной точки маршрута RouteSearchPoint обязательно указывать параметры objectId и levelId.
// объект RouteBuildOptions также необходим
let routeBuildOptions = RouteBuildOptions(
finishPoint: RouteSearchPoint(
coordinates : GeoPoint(latitude: 55.752425, longitude: 37.613983),
objectId: ..., // Можно получить из DgisMapObject
levelId: ..., // Можно получить из DirectoryObject или RenderedObjectInfo
),
// routeSearchOptions должен иметь тип PedestrianRouteSearchOptions
routeSearchOptions: PedestrianRouteSearchOptions()
)
Отображение маршрута
Если вы используете стандартный набор UI-элементов навигации, создавая стандартную фабрику INavigationViewControlsFactory, как рекомендуется в начале раздела, эта возможность уже доступна и не требует предварительной конфигурации.
Если вы используете собственные UI-элементы, для определения вхождения в режим навигации внутри здания можно пользоваться стандартными возможностями SDK, а именно подпиской на канал indoorChannel. Этот канал можно получить из NavigationManager.
Альтернативные маршруты
Во время движения по автомобильному маршруту навигатор периодически ищет другие способы доехать до финишной точки. При поиске альтернативных маршрутов учитываются все заданные параметры маршрута и промежуточные точки (кроме тех, которые пользователь уже посетил).
Альтернативный маршрут отображается на карте, когда развилка для съезда на него приближается по ходу движения, но при этом у пользователя остается достаточно времени на принятие решения.
Альтернативный маршрут показывается на карте в виде линии с бабликом, на котором показана разница во времени движения: сколько времени сэкономит или потеряет пользователь, если перейдёт на альтернативный маршрут. Для перехода на этот маршрут пользователь может нажать на линию маршрута на экране или просто свернуть на развилке на альтернативный маршрут. Если на развилке пользователь продолжает движение по основному маршруту, альтернативный маршрут исчезает с карты.
«Маршрут лучше»
Среди альтернативных маршрутов может быть выделен особый тип «маршрут лучше», если маршрут соответствует следующим критериям:
- Ожидаемая длительность движения по «маршруту лучше» должна быть существенно меньше, чем по оставшейся части основного маршрута. Разница по умолчанию — 5 минут.
- «Маршрут лучше» должен существенно отличаться от основного маршрута по геометрии (по дорогам, через которые он проходит). По умолчанию разница в длине отличающихся рёбер маршрута — 500 метров.
Если среди обнаруженных альтернативных маршрутов находится «маршрут лучше», на карте дополнительно отображается отдельный UI-элемент, при нажатии на который пользователь может перейти на движение по «маршруту лучше» или явно отказаться от него. Этот элемент исчезает с экрана через определённое время (по умолчанию 30 секунд), но линия маршрута продолжает отображаться на карте до проезда развилки.
В один и тот же момент времени только один маршрут может быть предложен пользователю как «маршрут лучше». Поиск нового «маршрута лучше» начинается либо после явного отказа пользователя от предыдущего (при нажатии на UI-элемент), либо после проезда развилки с ним.
Параметры поиска альтернативных маршрутов
Чтобы изменить параметры поиска альтернативных маршрутов, создайте объект AlternativeRoutesProviderSettings и передайте его при настройке NavigationManager:
self.navigationManager.alternativeRoutesProviderSettings.alternativeRoutesEnabled = true
Чтобы начать ведение по альтернативному маршруту, используйте AlternativeRouteSelector:
self.navigationManager.alternativeRouteSelector.selectAlternativeRoute(trafficRoute: route)
Динамическая информация о маршруте
В процессе навигации вы можете получать динамическую информацию о маршруте через свойство uiModel
в NavigationManager и отображать её в UI навигатора. В классе Model представлены следующие параметры:
- состояние навигатора (
state
иstateChannel
); - текущая геопозиция, с которой работает навигатор (
location
иlocationChannel
); - флаг использования текущей геопозиции для навигации (
locationAvailable
иlocationAvailableChannel
); - информация о маршруте с манёврами (
route
иrouteChannel
); - дорожные события и пробочные данные на маршруте (
dynamicRouteInfo
иdynamicRouteInfoChannel
); - текущая позиция пользователя на маршруте (
routePosition
иroutePositionChannel
); - флаг превышения скорости (
exceedingMaxSpeedLimit
иexceedingMaxSpeedLimitChannel
); - флаг нахождения «маршрута лучше» (
betterRoute
иbetterRouteChannel
); - расстояние (
distance
) и время (duration
) до конца маршрута; - флаг режима свободной навигации (
isFreeRoam
).
Информация о маршруте в Model изменяется динамически относительно текущей позиции пользователя на маршруте. Чтобы получать актуальную позицию, подпишитесь на канал routePositionChannel и затем используйте это значение для получения других данных о маршруте. Подробнее о работе с каналами см. в разделе Потоки значений.
Например, получите следующую информацию:
-
Расстояние (distance) и время (duration) от текущей точки до конца маршрута:
// Канал для получения позиции на маршруте let positionChannel = navigationManager.uiModel.routePositionChannel self.cancellable = positionChannel.sink { [weak self] routePosition in DispatchQueue.main.async { // Расстояние до конца маршрута в метрах let distance = navigationManager.uiModel.distance // Время до конца маршрута в секундах let duration = navigationManager.uiModel.duration ... } }
-
Дорожные события и данные о пробках (dynamicRouteInfo). Данные о дорожных событиях хранятся в контейнере RoadEventRouteAttribute, откуда элемент можно получить по позиции на маршруте.
Например, чтобы получить следующее ближайшее дорожное событие на маршруте:
// Канал для получения позиции на маршруте let positionChannel = navigationManager.uiModel.routePositionChannel self.cancellable = positionChannel.sink { [weak self] routePosition in DispatchQueue.main.async { // Получить ближайшее дорожное событие относительно текущей позиции let dynamicInfo = navigationManager.uiModel.dynamicRouteInfo let nearestEvent = dynamicInfo.roadEvents?.findNearForward(point: routePosition) ... } }
-
Текущее состояние навигатора. Вы можете получать его через подписку на канал stateChannel или отдельно (state):
// Получить состояние навигатора вне канала let currentState = navigationManager.uiModel.state
// Канал для получения состояния навигатора let stateChannel = navigationManager.uiModel.stateChannel self.cancellable = stateChannel.sink { state in DispatchQueue.main.async { ... } }
Навигатор может находиться в одном из следующих состояний:
disabled
— неактивен (начальное состояние);navigation
— в режиме ведения по маршруту;routeSearch
— в поиске нового маршрута;finished
— завершил ведение по маршруту (конечная точка достигнута).