Навигация | Mobile SDK | 2GIS Documentation
Android SDK

Навигация

С помощью SDK вы можете работать со следующими сценариями навигации:


Чтобы создать навигатор, используйте готовые элементы интерфейса и класс NavigationManager. Вы можете получать данные о текущем местоположении устройства одним из способов:

Добавьте в 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.

Вместе со стандартным источником данных о местоположении устройства 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:

  1. Обратитесь в службу поддержки 2ГИС для получения ключа доступа к Radar API. Обязательно укажите appId приложения, для которого будет создан ключ. Подробнее о получении ключа для работы с SDK см. в разделе Получение ключей доступа.

  2. Передайте параметр 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"))
    
  3. При создании объекта 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.
    • appIdappId приложения, на который распространяются ограничения ключа доступа. По умолчанию используется значение 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 для вычисления геопозиции, вы можете использовать один из способов:

  • Создайте 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              // Препятствия
    )