Android SDK Примеры (unstable) | 2GIS Documentation

Для работы с SDK нужно вызвать метод initialize() объекта DGis, указав контекст приложения и набор ключей доступа (объект ApiKeys).

В SDK используется два ключа: map (основной ключ SDK) и directory (ключ доступа к дополнительным API: справочнику объектов и маршрутизатору).

class Application : Application() {
    lateinit var sdkContext: Context

    override fun onCreate() {
        super.onCreate()

        sdkContext = DGis.initialize(
            this, ApiKeys(
                directory = "Directory API key",
                map = "SDK key"
            )
        )
    }
}

Дополнительно можно указать настройки журналирования (LogOptions) и настройки HTTP-клиента (HttpOptions), такие как кеширование.

// Ключи доступа
val apiKeys = ApiKeys(
    directory = "Directory API key",
    map = "SDK key"
)

// Настройки журналирования
val logOptions = LogOptions(
    LogLevel.VERBOSE
)

// Настройки HTTP-клиента
val httpOptions = HttpOptions(
    useCache = false
)

// Согласие на сбор и отправку персональных данных
val dataCollectConsent = PersonalDataCollectionConsent.GRANTED

sdkContext = DGis.initialize(
    appContext = this,
    apiKeys = apiKeys,
    dataCollectConsent = dataCollectConsent,
    logOptions = logOptions,
    httpOptions = httpOptions
)

Чтобы создать карту, добавьте MapView в ваш activity:

<ru.dgis.sdk.map.MapView
    android:id="@+id/mapView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:dgis_cameraTargetLat="55.740444"
    app:dgis_cameraTargetLng="37.619524"
    app:dgis_cameraZoom="16.0"
/>

Для карты можно указать начальные координаты (cameraTargetLat - широта; cameraTargetLng - долгота) и масштаб (cameraZoom).

MapView также можно создать программно. В таком случае настройки можно указать в виде объекта MapOptions.

Объект карты (Map) можно получить, вызвав метод getMapAsync():

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val sdkContext = DGis.initialize(applicationContext, apiKeys)
    setContentView(R.layout.activity_main)

    val mapView = findViewById<MapView>(R.id.mapView)
    lifecycle.addObserver(mapView)

    mapView.getMapAsync { map ->
        // Действия с картой
        val camera = map.camera
    }
}

Некоторые методы SDK (например те, которые обращаются к удалённому серверу) возвращают отложенные результаты (Future). Для работы с ними нужно создать обработчик получения данных и обработчик ошибок.

Пример получения объекта из справочника:

// Создание объекта для поиска по справочнику
val searchManager = SearchManager.createOnlineManager(sdkContext)

// Получение объекта из справочника по идентификатору
val future = searchManager.searchByDirectoryObjectId(objectId)

// Обработка результата
future.onResult { directoryObject ->
    Log.d("APP", "Название объекта: ${directoryObject.title}")
}

// Обработка ошибки
future.onError { error ->
    Log.d("APP", "Ошибка получения информации об объекте.")
}

По умолчанию обработка результатов происходит в UI-потоке. Чтобы это изменить, для onResult и onError можно указать Executor.

Подробнее про работу со справочником можно посмотреть в разделе Справочник объектов.

Некоторые объекты SDK предоставляют потоки значений, которые можно обработать, используя механизм каналов: на поток можно подписаться, указав функцию-обработчик данных, и отписаться, когда обработка данных больше не требуется. Для работы с потоками значений используется интерфейс Channel.

Пример подписки на изменение видимой области карты (поток новых прямоугольных областей):

// Выбираем канал (прямоугольники видимой области карты)
val visibleRectChannel = map.camera.visibleRectChannel

// Подписываемся и обрабатываем результаты в главной очереди. Значения будут присылаться при любом изменении видимой области до момента отписки.
// Важно сохранить соединение с каналом, иначе подписка будет уничтожена.
val connection = visibleRectChannel.connect { geoRect ->
    Log.d("APP", "${geoRect.southWestPoint.latitude.value}")
}

После окончания работы с каналом важно отменить подписку, чтобы избежать утечки памяти. Для этого нужно вызвать метод close():

connection.close()

В некоторых случаях для добавления объектов на карту нужно создать специальный объект - источник данных. Источники данных выступают в роли менеджеров объектов: вместо добавления объектов на карту напрямую, на карту добавляется источник данных и вся последующая работа с объектами происходит через него.

Источники данных бывают разных типов: движущиеся маркеры, маршруты с отображением текущей загруженности дорог, произвольные геометрические фигуры и т.д. Для каждого типа данных существует свой класс.

В общем случае работа с источниками данных выглядит следующим образом:

// Создание источника данных
val source = MyMapObjectSource(
    sdkContext,
    ...
)

// Добавление источника данных на карту
map.addSource(source)

// Добавление и удаление объектов в источнике данных
source.addObject(...)
source.removeObject(...)

Чтобы удалить созданный источник данных и все связанные с ним объекты, нужно вызвать метод карты removeSource():

map.removeSource(source)

Список активных источников данных можно получить, используя свойство map.sources.

Для добавления динамических объектов на карту (маркеров, линий, кругов, многоугольников) нужно создать менеджер объектов (MapObjectManager), указав объект карты. При удалении менеджера объектов удаляются все связанные с ним объекты на карте, поэтому его нужно сохранить в activity.

mapObjectManager = MapObjectManager(map)

Для добавления объектов используются методы addObject() и addObjects(). Для каждого динамического объекта можно указать поле userData, которое будет хранить произвольные данные, связанные с объектом. Настройки объектов можно менять после их создания.

Для удаления объектов используются методы removeObject() и removeObjects(). Чтобы удалить все объекты, можно использовать метод removeAll().

Чтобы добавить маркер на карту, нужно создать объект Marker, указав нужные настройки, и передать его в вызов addObject() менеджера объектов.

В настройках нужно указать координаты маркера (параметр position).

val marker = Marker(
    MarkerOptions(
        position = GeoPointWithElevation(
            latitude = 55.752425,
            longitude = 37.613983
        )
    )
)

mapObjectManager.addObject(marker)

Чтобы изменить иконку маркера, нужно указать объект Image в качестве параметра icon. Создать Image можно с помощью следующих функций:

val icon = imageFromResource(sdkContext, R.drawable.ic_marker)

val marker = Marker(
    MarkerOptions(
        position = GeoPointWithElevation(
            latitude = 55.752425,
            longitude = 37.613983
        ),
        icon = icon
    )
)

Чтобы изменить точку привязки иконки (выравнивание иконки относительно координат на карте), нужно указать параметр anchor.

Дополнительно можно указать текст для маркера и другие настройки (см. MarkerOptions).

Чтобы нарисовать на карте линию, нужно создать объект Polyline, указав нужные настройки, и передать его в вызов addObject() менеджера объектов.

Кроме списка координат для точек линии, в настройках можно указать ширину линии, цвет, пунктир, обводку и другие параметры (см. PolylineOptions).

// Координаты вершин ломаной линии
val points = listOf(
    GeoPoint(latitude = 55.7513, longitude = 37.6236),
    GeoPoint(latitude = 55.7405, longitude = 37.6235),
    GeoPoint(latitude = 55.7439, longitude = 37.6506)
)

// Создание линии
val polyline = Polyline(
    PolylineOptions(
        points = points,
        width = 2.lpx
    )
)

// Добавление линии на карту
mapObjectManager.addObject(polyline)

Свойство-расширение .lpx преобразует целое число в объект LogicalPixel.

Чтобы нарисовать на карте многоугольник, нужно создать объект Polygon, указав нужные настройки, и передать его в вызов addObject() менеджера объектов.

Координаты для многоугольника указываются в виде двумерного списка. Первый вложенный список должен содержать координаты основных вершин многоугольника. Остальные вложенные списки не обязательны и могут быть заданы для того, чтобы создать вырез внутри многоугольника (один дополнительный список - один вырез в виде многоугольника).

Дополнительно можно указать цвет полигона и параметры обводки (см. PolygonOptions).

val polygon = Polygon(
    PolygonOptions(
        contours = listOf(
            // Вершины многоугольника
            listOf(
                GeoPoint(latitude = 55.72014932919687, longitude = 37.562599182128906),
                GeoPoint(latitude = 55.72014932919687, longitude = 37.67555236816406),
                GeoPoint(latitude = 55.78004852149085, longitude = 37.67555236816406),
                GeoPoint(latitude = 55.78004852149085, longitude = 37.562599182128906),
                GeoPoint(latitude = 55.72014932919687, longitude = 37.562599182128906)
            ),
            // Координаты для выреза внутри многоугольника
            listOf(
                GeoPoint(latitude = 55.754167897761, longitude = 37.62422561645508),
                GeoPoint(latitude = 55.74450654680055, longitude = 37.61238098144531),
                GeoPoint(latitude = 55.74460317215391, longitude = 37.63435363769531),
                GeoPoint(latitude = 55.754167897761, longitude = 37.62422561645508)
            )
        ),
        borderWidth = 1.lpx
    )
)

mapObjectManager.addObject(polygon)

Для работы с камерой используется объект Camera, доступный через свойство map.camera.

Чтобы запустить анимацию перелёта камеры, нужно вызвать метод move() и указать параметры перелёта:

  • position - конечная позиция камеры (координаты и уровень приближения). Дополнительно можно указать наклон и поворот камеры (см. CameraPosition).
  • time - продолжительность перелёта в секундах (Duration).
  • animationType - тип анимации (CameraAnimationType).

Функция move() возвращает объект Future, который можно использовать, чтобы обработать событие завершения перелёта.

val mapView = findViewById<MapView>(R.id.mapView)

mapView.getMapAsync { map ->
    val cameraPosition = CameraPosition(
        point = GeoPoint(latitude = 55.752425, longitude = 37.613983),
        zoom = Zoom(16.0),
        tilt = Tilt(25.0),
        bearing = Arcdegree(85.0)
    )

    map.camera.move(cameraPosition, Duration.ofSeconds(2), CameraAnimationType.LINEAR).onResult {
        Log.d("APP", "Перелёт камеры завершён.")
    }
}

Для указания продолжительности перелёта можно использовать расширение .seconds:

map.camera.move(cameraPosition, 2.seconds, CameraAnimationType.LINEAR)

Для более точного контроля над анимацией перелёта можно использовать контроллер перелёта, который будет определять позицию камеры в каждый конкретный момент времени. Для этого нужно реализовать интерфейс CameraMoveController и передать созданный объект в метод move() вместо параметров перелёта.

Текущее состояние камеры (находится ли камера в полёте) можно получить, используя свойство state. См. CameraState для списка возможных состояний камеры.

val currentState = map.camera.state

Подписаться на изменения состояния камеры можно с помощью свойства stateChannel.

// Подписка
val connection = map.camera.stateChannel.connect { state ->
    Log.d("APP", "Состояние камеры изменилось на ${state}")
}

// Отписка
connection.close()

Текущую позицию камеры можно получить, используя свойство position (см. объект CameraPosition).

val currentPosition = map.camera.position

Log.d("APP", "Координаты: ${currentPosition.point}")
Log.d("APP", "Приближение: ${currentPosition.zoom}")
Log.d("APP", "Наклон: ${currentPosition.tilt}")
Log.d("APP", "Поворот: ${currentPosition.bearing}")

Подписаться на изменения позиции камеры (и угла наклона/поворота) можно с помощью свойства positionChannel.

// Подписка
val connection = map.camera.positionChannel.connect { position ->
    Log.d("APP", "Изменилась позиция камеры или угол наклона/поворота.")
}

// Отписка
connection.close()

На карту можно добавить специальный маркер, который будет отражать текущее местоположение устройства. Для этого нужно добавить на карту источник данных MyLocationMapObjectSource.

// Создание источника данных
val source = MyLocationMapObjectSource(
    sdkContext,
    MyLocationDirectionBehaviour.FOLLOW_SATELLITE_HEADING
)

// Добавление источника данных на карту
map.addSource(source)

Информацию об объектах на карте можно получить, используя пиксельные координаты. Для этого нужно вызвать метод карты getRenderedObjects(), указав координаты в пикселях и радиус в экранных миллиметрах. Метод вернет отложенный результат, содержащий информацию обо всех найденных объектах в указанном радиусе на видимой области карты (список RenderedObjectInfo).

Пример функции, которая принимает координаты нажатия на экран и передаёт их в метод getRenderedObjects():

override fun onTap(point: ScreenPoint) {
    map.getRenderedObjects(point, ScreenDistance(5f)).onResult { renderedObjectInfos ->
        // Первый объект в списке - самый близкий к координатам
        for (renderedObjectInfo in renderedObjectInfos) {
            Log.d("APP", "Произвольные данные объекта: ${renderedObjectInfo.item.item.userData}")
        }
    }
}

Для поиска объектов в справочнике нужно создать объект SearchManager, вызвав один из следующих методов:

  • SearchManager.createOnlineManager() - создаёт онлайн-справочник.
  • SearchManager.createOfflineManager() - создаёт офлайн-справочник, работающий только с предзагруженными данными.
  • SearchManager.createSmartManager() - создаёт комбинированный справочник, работающий с онлайн-данными при наличии сети и с предзагруженными данными при отсутствии сети.
val searchManager = SearchManager.createSmartManager(sdkContext)

Если идентификатор (ID) объекта известен, то для получения информации о нём нужно вызвать метод searchById(). Метод вернёт отложенный результат DirectoryObject.

searchManager.searchById(id).onResult { directoryObject ->
    Log.d("APP", "Название объекта: ${directoryObject.title}")
}

Если ID объекта не известен, то можно создать поисковый запрос (объект SearchQuery) с помощью SearchQueryBuilder и передать его в метод search(). Вызов вернёт отложенный результат SearchResult, содержащий список найденных объектов (DirectoryObject), разделенный на страницы.

val query = SearchQueryBuilder.fromQueryText("пицца").setPageSize(10).build()

searchManager.search(query).onResult { searchResult ->
    // Получаем первый объект с первой страницы
    val directoryObject = searchResult.firstPage?.items?.getOrNull(0) ?: return
    Log.d("APP", "Название объекта: ${directoryObject.title}")
}

Чтобы получить следующую страницу результатов поиска, нужно вызвать метод страницы fetchNextPage(), который вернёт отложенный результат Page.

firstPage.fetchNextPage().onResult { nextPage
    val directoryObject = nextPage?.items?.getOrNull(0) ?: return
}

Также с помощью справочника можно получать подсказки при текстовом поиске объектов (см. Suggest API для демонстрации). Для этого нужно создать объект SuggestQuery с помощью SuggestQueryBuilder и передать его в метод suggest(). Вызов вернёт отложенный результат SuggestResult, содержащий список подсказок (Suggest).

val query = SuggestQueryBuilder.fromQueryText("пицц").setLimit(10).build()

searchManager.suggest(query).onResult { suggestResult ->
    // Получаем первую подсказку из списка
    val firstSuggest = suggestResult.suggests?.getOrNull(0) ?: return
    Log.d("APP", "Заголовок подсказки: ${firstSuggest.title}")
}

Для того, чтобы проложить маршрут на карте, нужно создать два объекта: TrafficRouter для поиска оптимального маршрута и источник данных RouteMapObjectSource для отображения маршрута на карте.

Чтобы найти маршрут между двумя точками, нужно вызвать метод findRoute(), передав координаты точек в виде объектов RouteSearchPoint. Дополнительно можно указать параметры маршрута (RouteOptions), а также список промежуточных точек маршрута (список RouteSearchPoint).

val startSearchPoint = RouteSearchPoint(
    coordinates = GeoPoint(latitude = 55.759909, longitude = 37.618806)
)
val finishSearchPoint = RouteSearchPoint(
    coordinates = GeoPoint(latitude = 55.752425, longitude = 37.613983)
)

val trafficRouter = TrafficRouter(sdkContext)
val routesFuture = trafficRouter.findRoute(startSearchPoint, finishSearchPoint)

Вызов вернёт отложенный результат со списком объектов TrafficRoute. Чтобы отобразить найденный маршрут на карте, нужно на основе этих объектов создать объекты RouteMapObject и добавить их в источник данных RouteMapObjectSource.

// Создаём источник данных
val routeMapObjectSource = RouteMapObjectSource(sdkContext, RouteVisualizationType.NORMAL)
map.addSource(routeMapObjectSource)

// Ищем маршрут
val routesFuture = trafficRouter.findRoute(startSearchPoint, finishSearchPoint)
val trafficRouter = TrafficRouter(sdkContext)

// После получения маршрута добавляем его на карту
routesFuture.onResult { routes: List<TrafficRoute> ->
    var isActive = true
    var routeIndex = 0
    for (route in routes) {
        routeMapObjectSource.addObject(
            RouteMapObject(route, isActive, routeIndex)
        )
        isActive = false
        routeIndex++
    }
}

Вместо пары TrafficRouter и RouteMapObjectSource для построения маршрута можно использовать RouteEditor и RouteEditorSource. В таком случае не нужно обрабатывать список TrafficRoute, достаточно передать координаты маршрута в виде объекта RouteParams в метод setRouteParams() и маршрут отобразится автоматически.

val routeEditor = RouteEditor(sdkContext)
val routeEditorSource = RouteEditorSource(sdkContext, routeEditor)
map.addSource(routeEditorSource)

routeEditor.setRouteParams(
    RouteParams(
        startPoint = RouteSearchPoint(
            coordinates = GeoPoint(latitude = 55.759909, longitude = 37.618806)
        ),
        finishPoint = RouteSearchPoint(
            coordinates = GeoPoint(latitude = 55.752425, longitude = 37.613983)
        )
    )
)

В рамках SDK можно использовать произвольный источник геопозиции. Для этого нужно реализовать интерфейс LocationSource.

public class CustomLocationSource: LocationSource {
    override fun activate(listener: LocationChangeListener?) {
        // Включение источника геопозиции
    }

    override fun deactivate() {
        // Выключение источника геопозиции
    }

    override fun setDesiredAccuracy(accuracy: DesiredAccuracy?) {
        // Изменение требуемого уровня точности
    }
}

Чтобы зарегистрировать созданный источник в SDK, нужно вызвать функцию registerPlatformLocationSource().

val customSource = CustomLocationSource()
registerPlatformLocationSource(sdkContext, customSource)

Основная точка входа в интерфейс - функция activate(). Когда SDK потребуется геопозиция, в эту функцию будет передан объект LocationChangeListener. После этого, чтобы сообщить текущую геопозицию, нужно передать в него массив объектов Location (от более старой позиции к более новой), используя метод onLocationChanged().

val location = Location(...)
val newLocations = arrayOf(location)
listener.onLocationChanged(newLocations)

Чтобы сообщить изменение доступности источника, можно вызвать метод onAvailabilityChanged().

Дополнительно можно менять логику определения геопозиции в зависимости от требуемой точности. Требуемая точность передаётся в функцию setDesiredAccuracy() в виде объекта DesiredAccuracy.

Когда источник геопозиции больше не требуется, будет вызвана функция deactivate().