Навигация
С помощью SDK вы можете работать со следующими сценариями навигации:
- Запустить навигатор в разных режимах (свободная навигация, ведение по маршруту и симуляция ведения), в том числе внутри зданий.
- Предлагать пользователю альтернативные маршруты по пути.
- Получать динамические данные маршрута в ходе навигации.
- Настраивать звуковые оповещения в навигаторе.
Чтобы создать навигатор, используйте готовые элементы интерфейса и класс NavigationManager. Вы можете получать данные о текущем местоположении устройства одним из способов:
- Использовать стандартный источник данных и при необходимости включить определение местоположения через Radar API.
- Настроить навигатор для работы с собственным источником геопозиции.
Начало работы
Добавьте в MapView элементы NavigationView и DefaultNavigationControls.
<ru.dgis.sdk.map.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ru.dgis.sdk.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ru.dgis.sdk.navigation.DefaultNavigationControls
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ru.dgis.sdk.navigation.NavigationView>
</ru.dgis.sdk.map.MapView>
Чтобы у SDK был доступ к данным о текущем местоположении устройства, используйте стандартный источник данных DefaultLocationSource или создайте собственный источник геопозиции. Зарегистрируйте источник данных в SDK: вызовите метод registerPlatformLocationSource() и передайте в него контекст SDK и источник.
Добавьте на карту маркер с текущей геопозицией устройства с помощью источника данных MyLocationMapObjectSource и передайте его в метод карты addSource().
Создайте объект NavigationManager.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sdkContext = DGis.initialize(applicationContext, apiKeys)
// Создание и регистрация в SDK источника данных о текущей геопозиции
locationProvider = DefaultLocationSource(applicationContext)
registerPlatformLocationSource(sdkContext, locationProvider)
setContentView(R.layout.activity_navigation)
findViewById<MapView>(R.id.mapView).apply { mapView ->
lifecycle.addObserver(mapView)
mapView.getMapAsync { map ->
// Добавление маркера с текущей геопозицией на карту
map.addSource(
MyLocationMapObjectSource(
sdkContext,
MyLocationDirectionBehaviour.FOLLOW_SATELLITE_HEADING,
createSmoothMyLocationController()
)
)
}
}
// Создание объекта NavigationManager
navigationManager = NavigationManager(sdkContext)
findViewById<NavigationView>(R.id.navigationView).apply {
// Привязка созданного объекта NavigationManager к элементу интерфейса NavigationView
navigationManager = this@NavigationActivity.navigationManager
}
// Запуск навигатора в режиме свободной навигации
navigationManager.start()
}
Навигатор может работать в трёх режимах: свободная навигация, ведение по маршруту и симуляция ведения.
Настройки навигатора можно изменить через свойства объекта NavigationManager.
Использование Radar API
Вместе со стандартным источником данных о местоположении устройства DefaultLocationSource вы можете использовать Radar API. Radar API вычисляет местоположение устройства по сотовым вышкам и ближайшим точкам доступа Wi-Fi.
Если включено использование Radar API, то постоянно сравнивается точность геопозиции, полученная из GPS и через Radar API. По умолчанию приоритетной являются геопозиция, вычисленная с помощью GPS. Геопозиция, вычисленная через Radar API, используется в следующих случаях:
- Потеря сигнала GPS.
- Ухудшение точности GPS до значения параметра
minimalAcceptableGpsAccuracyM
, который передаётся при инициализацииDefaultLocationSource
. При этом точность геопозиции из Radar API должна быть выше, чем у GPS.
Разрешения для приложения
Для правильной работы Radar API приложение должно получить разрешения:
- ACCESS_FINE_LOCATION;
- ACCESS_WIFI_STATE;
- CHANGE_WIFI_STATE;
- NEARBY_WIFI_DEVICES (опционально) — используется для расчёта расстояния до точки доступа Wi-Fi. Разрешение является чувствительным, поэтому приложение запрашивает и получает его самостоятельно. Сервис может работать без этого разрешения, но в некоторых случаях оно увеличивает точность геопозиции.
Включение Radar API
Чтобы использовать Radar API:
-
Обратитесь в службу поддержки 2ГИС для получения ключа доступа к Radar API. Обязательно укажите
appId
приложения, для которого будет создан ключ. Подробнее о получении ключа для работы с SDK см. в разделе Получение ключей доступа. -
Передайте параметр RadarApiSettings при создании DefaultLocationSource:
import ru.dgis.sdk.positioning.DefaultLocationSource import ru.dgis.sdk.positioning.radar.RadarApiSettings val source = DefaultLocationSource(this, RadarApiSettings.ON(apiKey = "your-api-key"))
-
При создании объекта
RadarApiSettings.ON
вы можете дополнительно настроить поведение работы сервиса. Пример с полным набором параметров:data class ON( val apiKey: String, val baseUrl: String = "https://radar.api.2gis.com", val appId: String? = null, val httpClient: RadarApiHttpClient = DefaultHttpClient(), val idlePollTimeoutMs: Int = 15_000, val activePollTimeoutMs: Int = 1_000, val minimalAcceptableGpsAccuracyM: Int = 99, ) : RadarApiSettings()
Здесь:
apiKey
— ключ доступа к Radar API.baseUrl
— базовый URL API. По умолчанию используетсяhttps://radar.api.2gis.com
.appId
—appId
приложения, на который распространяются ограничения ключа доступа. По умолчанию используется значениеapplicationId
для текущего приложения. Если в ключе используется Wildcard и он не совпадает с текущимapplicationId
, или если выставлены ограничения на другойappId
, укажите этотappId
.httpClient
— HTTP-клиент для выполнения запросов. По умолчанию используется стандартный DefaultHttpClient.idlePollTimeoutMs
— таймаут между запросами в неактивном состоянии в миллисекундах. Применяется, когда Radar API не является основным источником геопозиции (например, когда точность GPS выше). Для поддержания актуальности данных запросы геопозиции осуществляются не чаще этого периода.activePollTimeoutMs
— таймаут между запросами в активном состоянии в миллисекундах. Применяется, когда система решает, что предпочтительнее геопозиция, полученная из Radar API. Сервис переходит в активный режим и отправляет запросы Radar API не чаще этого периода. При этом запросы могут отправляться ещё реже, если не меняются данные, полученные с датчиков устройства (например, устройство находится на месте).minimalAcceptableGpsAccuracyM
— минимально допустимая точность GPS в метрах. Если точность GPS выше или равна этому значению, то источник данных GPS считается предпочтительным, даже если точность геопозиции из Radar API выше.
Отключение Radar API
Чтобы отключить Radar API для вычисления геопозиции, вы можете использовать один из способов:
-
Создайте
DefaultLocationSource
, передав только контекст приложения:val source = DefaultLocationSource(this)
-
Передайте в
DefaultLocationSource
объектRadarApiSettings.OFF
:val source = DefaultLocationSource(this, RadarApiSettings.OFF)
Собственный источник геопозиции
В качестве источника данных о текущем местоположении вы можете использовать собственный источник геопозиции. Для этого реализуйте интерфейс LocationSource и зарегистрируйте созданный источник в SDK с помощью метода registerPlatformLocationSource().
// Создание источника данных о текущем местоположении
val customSource = CustomLocationSource()
// Регистрация источника данных в SDK
registerPlatformLocationSource(sdkContext, customSource)
Когда для SDK требуется геопозиция, в функцию activate()
передаётся объект LocationChangeListener.
public class CustomLocationSource: LocationSource {
// Включение источника геопозиции
override fun activate(listener: LocationChangeListener?) {
}
...
}
После этого, чтобы сообщить текущую геопозицию, передайте в LocationChangeListener
массив объектов Location (от более старой геопозиции к более новой) с помощью метода onLocationChanged().
val location = Location(...)
val newLocations = arrayOf(location)
listener.onLocationChanged(newLocations)
Чтобы сообщить об изменении доступности источника, используйте метод onAvailabilityChanged().
Дополнительно вы можете менять логику определения геопозиции в зависимости от требуемой точности и передавать её в метод setDesiredAccuracy()
в виде объекта DesiredAccuracy.
Когда источник геопозиции больше не требуется, будет вызвана функция deactivate()
.
public class CustomLocationSource: LocationSource {
// Включение источника геопозиции
override fun activate(listener: LocationChangeListener?) {
}
// Выключение источника геопозиции
override fun deactivate() {
}
// Изменение требуемого уровня точности
override fun setDesiredAccuracy(accuracy: DesiredAccuracy?) {
}
}
Свободная навигация
В этом режиме маршрут следования отсутствует, но навигатор будет информировать о превышениях скорости, дорожных камерах, авариях и ремонтных работах.
Чтобы запустить навигатор в этом режиме, нужно вызвать метод start()
без параметров.
navigationManager.start()
Ведение по маршруту
В этом режиме на карте будет построен маршрут от текущего местоположения до указанной точки назначения, и пользователь будет получать инструкции по мере движения.
Чтобы запустить навигатор в этом режиме, нужно вызвать метод start()
и указать объект RouteBuildOptions: координаты точки назначения и настройки маршрута.
val routeBuildOptions = RouteBuildOptions(
finishPoint = RouteSearchPoint(
coordinates = GeoPoint(latitude = 55.752425, longitude = 37.613983)
),
routeSearchOptions = CarRouteSearchOptions(
avoidTollRoads = true,
avoidUnpavedRoads = false,
avoidFerry = false,
routeSearchType = RouteSearchType.JAM
)
)
navigationManager.start(routeBuildOptions)
Дополнительно при вызове метода start()
можно указать объект TrafficRoute — готовый маршрут для навигации (см. детали в разделе Маршруты). В таком случае навигатор не будет пытаться построить маршрут от текущего местоположения, а начнёт ведение по указанному маршруту.
navigationManager.start(routeBuildOptions, trafficRoute)
Симуляция ведения по маршруту
В этом режиме навигатор не будет отслеживать реальное местоположение устройства, а запустит симулированное движение по указанному маршруту. Режим удобно использовать для отладки.
Чтобы запустить навигатор в режиме симуляции, нужно вызвать метод startSimulation()
, указав готовый маршрут (TrafficRoute) и его настройки (RouteBuildOptions).
Скорость движения можно изменить с помощью свойства SimulationSettings.speed (метры в секунду).
navigationManager.simulationSettings.speed = 30 / 3.6
navigationManager.startSimulation(routeBuildOptions, trafficRoute)
Остановить симуляцию можно с помощью метода stop()
.
navigationManager.stop()
Навигация внутри зданий
SDK предоставляет возможность строить маршруты внутри некоторых зданий, для которых существуют этажные планы. Например, можно проложить маршрут до конкретного магазина в торговом центре, составить сложный маршрут между несколькими точками и тд.
Этот тип навигации включен в набор стандартной поставки Full SDK, однако имеет ряд ограничений:
- Может быть активирован только в режиме пешеходной навигации
- Из-за особенностей работы современных систем позиционирования, возможности определения позиции внутри зданий сильно ограничены. Когда система определит, что устройство попало в пределы здания, слежение за геопозицей и ее отображение отключится, оставив построенный маршрут и рекомендации по проходу. Мы работаем над преодолением этих трудностей и надеемся предоставить надежный способ определения местоположения внутри зданий в будущем.
Создание маршрута
Создание маршрута происходит по принципам, описанным в разделе Ведение по маршруту с несколькими важными дополнениями. При создании конечной точки маршрута RouteSearchPoint обязательно указывать параметры objectId и levelId.
// объект RouteBuildOptions также необходим
val routeBuildOptions = RouteBuildOptions(
finishPoint = RouteSearchPoint(
coordinates = GeoPoint(latitude = 55.752425, longitude = 37.613983),
objectId = ..., // Можно получить из DgisMapObject
levelId = ..., // Можно получить из DirectoryObject или RenderedObjectInfo
),
// routeSearchOptions должен иметь тип PedestrianRouteSearchOptions
routeSearchOptions = PedestrianRouteSearchOptions()
)
Отображение маршрута
Если вы используете стандартный набор из NavigationView и DefaultNavigationControls, как рекомендуется в начале раздела, эта возможность уже доступна и не требует предварительной конфигурации.
Если вы используете собственные UI-элементы, для определения вхождения в режим навигации внутри здания можно пользоваться стандартными возможностями SDK, а именно подпиской на канал indoorChannel. Этот канал можно получить из NavigationManager'a.
Альтернативные маршруты
Во время движения по автомобильному маршруту навигатор периодически ищет другие способы доехать до финишной точки. При поиске альтернативных маршрутов учитываются все заданные параметры маршрута и промежуточные точки (кроме тех, которые пользователь уже посетил). Интервал поиска составляет 1 минуту и не может быть изменён.
Альтернативный маршрут отображается на карте, когда развилка для съезда на него приближается по ходу движения, но при этом у пользователя остается достаточно времени на принятие решения.
Альтернативный маршрут показывается на карте в виде линии с бабликом, на котором показана разница во времени движения: сколько времени сэкономит или потеряет пользователь, если перейдёт на альтернативный маршрут. Для перехода на этот маршрут пользователь может нажать на линию маршрута на экране или просто свернуть на развилке на альтернативный маршрут. Если на развилке пользователь продолжает движение по основному маршруту, альтернативный маршрут исчезает с карты.
Чтобы начать ведение по альтернативному маршруту, используйте AlternativeRouteSelector:
navigationManager.alternativeRouteSelector.selectAlternativeRoute(route)
«Маршрут лучше»
Среди альтернативных маршрутов может быть выделен особый тип «маршрут лучше», если маршрут соответствует следующим критериям:
- Ожидаемая длительность движения по «маршруту лучше» должна быть существенно меньше, чем по оставшейся части основного маршрута. Разница по умолчанию — 5 минут.
- «Маршрут лучше» должен существенно отличаться от основного маршрута по геометрии (по дорогам, через которые он проходит). По умолчанию разница в длине отличающихся рёбер маршрута — 500 метров.
Если среди обнаруженных альтернативных маршрутов находится «маршрут лучше», на карте дополнительно отображается отдельный UI-элемент, при нажатии на который пользователь может перейти на движение по «маршруту лучше» или явно отказаться от него. Этот элемент исчезает с экрана через определённое время (по умолчанию 30 секунд), но линия маршрута продолжает отображаться на карте до проезда развилки.
В один и тот же момент времени только один маршрут может быть предложен пользователю как «маршрут лучше». Поиск нового «маршрута лучше» начинается либо после явного отказа пользователя от предыдущего (при нажатии на UI-элемент), либо после проезда развилки с ним.
Настройки поиска альтернативных маршрутов
Чтобы изменить параметры поиска альтернативных маршрутов, используйте свойство AlternativeRoutesProviderSettings объекта NavigationManager.
Например, чтобы отключить поиск альтернативных маршрутов и «маршрута лучше», используйте параметры alternativeRoutesEnabled
и betterRouteEnabled
соответственно:
// Отключить поиск альтернативных маршрутов
navigationManager.alternativeRoutesProviderSettings.alternativeRoutesEnabled = false
// Отключить поиск «маршрута лучше»
navigationManager.alternativeRoutesProviderSettings.betterRouteEnabled = false
Поиск «маршрута лучше» не может быть включён самостоятельно, если не включён поиск альтернативных маршрутов в целом.
Динамическая информация о маршруте
В процессе навигации вы можете получать динамическую информацию о маршруте через свойство uiModel
в NavigationManager и отображать её в UI навигатора. В классе Model представлены следующие параметры:
- состояние навигатора (
state
иstateChannel
); - текущая геопозиция, с которой работает навигатор (
location
иlocationChannel
); - флаг использования текущей геопозиции для навигации (
locationAvailable
иlocationAvailableChannel
); - информация о маршруте с манёврами (
route
иrouteChannel
); - дорожные события и пробочные данные на маршруте (
dynamicRouteInfo
иdynamicRouteInfoChannel
); - текущая позиция пользователя на маршруте (
routePosition
иroutePositionChannel
); - флаг превышения скорости (
exceedingMaxSpeedLimit
иexceedingMaxSpeedLimitChannel
); - флаг нахождения «маршрута лучше» (
betterRoute
иbetterRouteChannel
); - время до конца маршрута (
duration
); - флаг режима свободной навигации (
isFreeRoam
).
Информация о маршруте в Model изменяется динамически относительно текущей позиции пользователя на маршруте. Чтобы получать актуальную позицию, подпишитесь на канал routePositionChannel и затем используйте это значение для получения других данных о маршруте. Подробнее о работе с каналами см. в разделе Потоки значений.
Например, получите следующую информацию:
-
Время (duration) от текущей точки до конца маршрута:
// Канал для получения позиции на маршруте val positionChannel = navigationManager.uiModel.routePositionChannel val postionSubscription = positionChannel.connect { // Время до конца маршрута в секундах val duration = navigationManager.uiModel.duration ... }
-
Дорожные события и данные о пробках (dynamicRouteInfo). Данные о дорожных событиях хранятся в контейнере RoadEventRouteAttribute, откуда элемент можно получить по позиции на маршруте.
Например, чтобы получить следующее ближайшее дорожное событие на маршруте:
// Канал для получения позиции на маршруте val positionChannel = navigationManager.uiModel.routePositionChannel val positionSubscription = positionChannel.connect { routePosition -> // Получить ближайшее дорожное событие относительно текущей позиции val dynamicInfo = navigationManager.uiModel.dynamicRouteInfo val nearestEvent = routePosition?.let { dynamicInfo.roadEvents.findNearForward(it) } ... }
-
Текущее состояние навигатора. Вы можете получать его через подписку на канал stateChannel или отдельно (state):
// Получить состояние навигатора вне канала val currentState = navigationManager.uiModel.state
// Канал для получения состояния навигатора val stateChannel = navigationManager.uiModel.stateChannel val stateSubscription = stateChannel.connect { ... }
Навигатор может находиться в одном из следующих состояний:
DISABLED
— неактивен (начальное состояние);NAVIGATION
— в режиме ведения по маршруту;ROUTE_SEARCH
— в поиске нового маршрута;FINISHED
— завершил ведение по маршруту (конечная точка достигнута).
Звуковые оповещения
Вы можете настроить звуковые оповещения в навигаторе через свойство soundNotificationSettings
объекта NavigationManager. В классе SoundNotificationSettings представлены следующие параметры:
-
Уровень громкости (
notificationVolume
):navigationManager.soundNotificationSettings.notificationVolume = 10 // Значение от 0 до 100
-
Отключение звука (
mute
):navigationManager.soundNotificationSettings.mute = true // Звук отключен
-
Категории звуковых оповещений (
enabledSoundCategories
). Примеры категорий событий: дорожные работы (ROAD_WORKS
), платные дороги (TOLLS
), препятствия (OBSTACLES
) и другие (см. полный список в SoundCategory).По умолчанию все категории оповещений включены. Чтобы включить оповещения только для определённых событий, укажите их:
navigationManager.soundNotificationSettings.enabledSoundCategories = EnumSet.of( SoundCategory.CROSSROAD_CAMERAS, // Камеры контроля перекрёстка SoundCategory.AVERAGE_SPEED_CAMERAS, // Камеры контроля средней скорости SoundCategory.ROAD_WORKS, // Дорожные работы SoundCategory.TOLLS, // Платные дороги SoundCategory.OBSTACLES // Препятствия )