Перейти к основному содержимому

Карта

Создание карты

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

В настройках важно указать корректное для устройства значение PPI. Его можно найти в спецификации устройства. По умолчанию выставлено значение DevicePpi.autodetected.

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

// Настройки карты.
var mapOptions = MapOptions.default

// Значение PPI для устройства.
// По умолчанию, mapOptions.devicePPI == DevicePpi.autodetected.
mapOptions.devicePPI = devicePPI

// Создание фабрики объектов карты.
let mapFactory: DGis.IMapFactory = try sdk.makeMapFactory(options: mapOptions)

Получить слой карты можно через свойство mapView. Контроллер карты доступен через свойство map (см. класс Map).

// Слой карты.
let mapView: UIView & IMapView = mapFactory.mapView

// Контроллер карты.
let map = mapFactory.map

Пример UIViewController для отображения карты:

import Foundation
import DGis
import UIKit

class MapViewController: UIViewController {
private lazy var sdk = DGis.Container()
private var dataLoadingStateCancellable: ICancellable = NoopCancellable()

override func viewDidLoad() {
do {
var mapOptions = MapOptions.default
mapOptions.devicePPI = .autodetected
let mapFactory = try sdk.makeMapFactory(options: mapOptions)
let mapView: UIView & IMapView = mapFactory.mapView
mapView.frame = self.view.bounds
self.view.addSubview(mapView)

/// Стартовую позицию камеры можно рассчитывать только после загрузки карты.
self.dataLoadingStateCancellable = mapFactory.map.dataLoadingStateChannel.sink { loadingState in
if loadingState == .loaded {
print("Now map is loaded")
}
}
} catch let error as SDKError {
print(error.description)
} catch {
print("System error: \(error)")
}
}
}

Важное замечание: объект DGis.Container должен создаваться в одном экземпляре и храниться на уровне UIViewController.

Офлайн-режим

Чтобы настроить карту для работы в офлайн-режиме:

  1. Выполните подготовительные шаги, чтобы карта могла работать с предзагруженными данными.

  2. Добавьте источник данных для карты. Для этого в функции createDgisSource() установите параметру workingMode одно из следующих значений:

    • OFFLINE — чтобы всегда использовать только предварительно загруженные данные.
    • HYBRID_ONLINE_FIRST — чтобы преимущественно использовать онлайн-данные с серверов 2ГИС. Предзагруженные данные используются, только когда они совпадают с онлайн-данными или когда данные с серверов получить невозможно.
    • HYBRID_OFFLINE_FIRST — чтобы преимущественно использовать предзагруженные данные. Онлайн-данные с серверов 2ГИС используются, только когда отсутствуют предзагруженные данные.
    let sources = [DgisSource.createDgisSource(context: sdkContext, workingMode: .offline)]
  3. При создании карты укажите созданный источник в MapOptions:

    var mapOptions = MapOptions.default
    mapOptions.sources = sources
    let mapFactory = try sdk.makeMapFactory(options: mapOptions)
    let mapView = mapFactory.mapView

Кадровая частота

Чтобы получить FPS выше 60 при отображении карты на поддерживаемых устройствах, добавьте ключ CADisableMinimumFrameDurationOnPhone со значением true в файл info.plist вашего приложения.

Добавление объектов

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

// Сохраняем объект в свойство, так как при удалении менеджера исчезают все связанные с ним объекты на карте.
self.objectManager = MapObjectManager(map: map)

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

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

MapObjectManager — это контейнер объектов. Пока объекты нужны на карте, MapObjectManager необходимо сохранять на уровне класса.

Маркер

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

Иконку для маркера можно создать с помощью метода make() фабрики изображений (IImageFactory), используя UIImage, PNG-данные или SVG-разметку.

// Иконка на основе UIImage.
let uiImage = UIImage(systemName: "umbrella.fill")!.withTintColor(.systemRed)
let icon = sdk.imageFactory.make(image: uiImage)

// Иконка на основе SVG-данных.
let icon = sdk.imageFactory.make(svgData: imageData, size: imageSize)

// Иконка на основе PNG-данных (быстрее, чем из UIImage).
let icon = sdk.imageFactory.make(pngData: imageData, size: imageSize)

// Настройки маркера.
let options = MarkerOptions(
position: GeoPointWithElevation(
latitude: 55.752425,
longitude: 37.613983
),
icon: icon
)

// Создание и добавление маркера.
let marker = try Marker(options: options)
objectManager.addObject(object: marker)

Чтобы изменить настройки уже созданного маркера, установите новые значения для параметров объекта Marker: cм. полный список доступных параметров в описании Marker.

// Изменение позиции маркера
marker.position = GeoPointWithElevation(latitude: 59.93428, longitude: 30.33510)

// Изменение иконки
let uiImage = UIImage(systemName: "bubble.right.fill")!.withTintColor(.systemGreen)
let newIcon = sdk.imageFactory.make(image: uiImage)
marker.icon = newIcon

// Изменение точки привязки иконки
marker.anchor = Anchor(x: 0.5, y: 0.5)

// Изменение прозрачности иконки
marker.iconOpacity = Opacity(value: 1.0)

// Изменение подписи маркера
marker.text = "Новый текст"

// Изменения стиля подписи
marker.textStyle = TextStyle(
fontSize: LogicalPixel(value: 8),
color: Color(argb: 4294967295),
strokeWidth: LogicalPixel(value: 0.3499999940395355),
strokeColor: Color(argb: 4294967295),
textPlacement: TextPlacement.bottomCenter,
textOffset: LogicalPixel(value: 0),
fontName: nil
)

// Изменение флага перемещаемости маркера
marker.isDraggable = true

// Изменение целевой ширины маркера
marker.iconWidth = LogicalPixel(value: 2.0)

// Изменение угла поворота маркера на карте относительно направления на север
marker.iconMapDirection = MapDirection(value: 10)

// Изменение флага анимации для появления маркера
marker.animatedAppearance = true

Линия

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

// Координаты вершин ломаной линии.
let points = [
GeoPoint(latitude: 55.7513, longitude: value: 37.6236),
GeoPoint(latitude: 55.7405, longitude: value: 37.6235),
GeoPoint(latitude: 55.7439, longitude: value: 37.6506)
]

// Настройки линии.
let options = PolylineOptions(
points: points,
width: LogicalPixel(value: 2),
color: DGis.Color.black
)

// Создание и добавление линии.
let polyline = try Polyline(options: options)
objectManager.addObject(object: polyline)

Чтобы изменить настройки уже созданной линии, установите новые значения для параметров объекта Polyline: cм. полный список доступных параметров в описании Polyline.

// Изменение координат вершин линии
polyline.points = [
GeoPoint(latitude: 55.7513, longitude: value: 37.6236),
GeoPoint(latitude: 55.7405, longitude: value: 37.6235),
GeoPoint(latitude: 55.7439, longitude: value: 37.6506)
]

// Изменение толщины линии
polyline.width = LogicalPixel(value: 3)

// Изменение цвета линии
polyline.color = Color(argb: 4294967295)

// Стирание части линии
polyline.erasedPart = 0.8

// Изменение параметров пунктирной линии
polyline.dashedPolylineOptions = DashedPolylineOptions(
dashLength: LogicalPixel(value: 5),
dashSpaceLength: LogicalPixel(value: 2)
)

// Изменение параметров градиентной линии
polyline.gradientPolylineOptions = GradientPolylineOptions(
borderWidth: LogicalPixel(value: 0),
secondBorderWidth: LogicalPixel(value: 0),
gradientLength: LogicalPixel(value: 1),
borderColor: Color(argb: 4294967295),
secondBorderColor: Color(argb: 4294967295),
colors: [Color],
colorIndices: Data
)

Многоугольник

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

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

// Настройки многоугольника.
let options = PolygonOptions(
contours: [
// Вершины многоугольника.
[
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)
],
// Координаты выреза внутри многоугольника.
[
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)
]
],
color: DGis.Color.black,
strokeWidth: LogicalPixel(value: 2)
)

// Создание и добавление многоугольника.
let polygon = try Polygon(options: options)
objectManager.addObject(object: polygon)

Чтобы изменить настройки уже созданного многоугольника, установите новые значения для параметров объекта Polygon: cм. полный список доступных параметров в описании Polygon.

// Изменение вершин многоугольника
polygon.contours = [
GeoPoint(latitude: 55.72014932919687, longitude: 37.562599182128906),
GeoPoint(latitude: 55.72014932919687, longitude: 37.67555236816406),
GeoPoint(latitude: 55.72014932919687, longitude: 37.562599182128906)
]

// Изменение цвета заливки многоугольника
polygon.color = Color(argb: 4294967295)

// Изменение толщины границы многоугольника
polygon.strokeWidth = LogicalPixel(value: 2)

// Изменение цвета границы многоугольника
polygon.strokeColor = Color(argb: 4294967295)

Окружность

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

// Настройки окружности
let options = CircleOptions(
position: GeoPoint(latitude: 55.72014932919687, longitude: 37.562599182128906),
radius: Meter(value: 10)
)

// Создание и добавление окружности
let circle = try Circle(options: options)
objectManager.addObject(object: circle)

Чтобы изменить настройки уже созданной окружности, установите новые значения для параметров объекта Circle: cм. полный список доступных параметров в описании Circle.

// Изменение центра окружности
circle.position = GeoPoint(latitude: 55.74460317215391, longitude: 37.63435363769531)

// Изменение радиуса окружности
circle.radius = Meter(value: 1)

// Изменение цвета заливки окружности
circle.color = Color(argb: 4294967295)

// Изменение толщины границы окружности
circle.strokeWidth = LogicalPixel(value: 2)

// Изменение цвета границы окружности
circle.strokeColor = Color(argb: 4294967295)

Добавление нескольких объектов

Если на карту необходимо добавить коллекцию объектов, то добавление через метод addObject в цикле по всей коллекции приведет к потере производительности. Для добавления коллекции объектов нужно сначала подготовить всю коллекцию и добавить её через метод addObjects:

// подготавливаем коллекцию объектов
let options = [MarkerOptions(<params>), MarkerOptions(<params>)]
var markers: [SimpleMapObject] = []
options.forEach{ option in
markers.append(try Marker(options: option))
}

// добавляем коллекцию объектов на карту
mapObjectManager.addObjects(objects: markers)

Кластеризация

Кластеризация — это визуальная группировка близко расположенных друг к другу объектов (маркеров) в один кластер при уменьшении масштаба карты. Группировка происходит постепенно: чем меньше масштаб карты, тем меньше кластеров. Кластер отображается как маркер с цифрой, соответствующий количеству объектов в кластере.

Чтобы добавить на карту маркеры в режиме кластеризации, создайте менеджер объектов (MapObjectManager) с помощью метода MapObjectManager.withClustering() и укажите следующие свойства:

  • Экземпляр карты (map).
  • Минимальное расстояние между маркерами в логических пикселях на уровнях масштабирования, при которых работает кластеризация (logicalPixel).
  • Уровень масштабирования, при котором и выше видны только отдельные маркеры, без кластеров (maxZoom).
  • Уровень масштабирования, при котором и ниже перестают формироваться новые кластеры (minZoom).
  • Пользовательскую реализацию протокола SimpleClusterRenderer, который используется для кастомизации кластеров в MapObjectManager.
final class SimpleClusterRendererImpl: SimpleClusterRenderer {
private let image: DGis.Image
private var idx = 0

init(
image: DGis.Image
) {
self.image = image
}

func renderCluster(cluster: SimpleClusterObject) -> SimpleClusterOptions {
let textStyle = TextStyle(
fontSize: LogicalPixel(15.0),
textPlacement: TextPlacement.rightTop
)
let objectCount = cluster.objectCount
let iconMapDirection = objectCount < 5 ? MapDirection(value: 45.0) : nil
idx += 1
return SimpleClusterOptions(
icon: self.image,
iconMapDirection: iconMapDirection,
text: String(objectCount),
textStyle: textStyle,
iconWidth: LogicalPixel(30.0),
userData: idx,
zIndex: ZIndex(value: 6),
animatedAppearance: false
)
}
}

self.objectManager = MapObjectManager.withClustering(
map: map,
logicalPixel: LogicalPixel(80.0),
maxZoom: Zoom(19.0),
minZoom: Zoom(8.0)
clusterRenderer: SimpleClusterRendererImpl(image: self.icon)
)

После создания менеджера объектов с кластеризацией вы можете добавлять маркеры обычными способами через addObject() или addObjects().

Генерализация

Генерализация — это визуальная группировка близко расположенных друг к другу объектов (маркеров) так, что при уменьшении масштаба карты вместо нескольких маркеров отображается один «ключевой». Группировка происходит постепенно: чем меньше масштаб карты, тем меньше групп.

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

  • Экземпляр карты (map).
  • Минимальное расстояние между маркерами в логических пикселях на уровнях масштабирования, при которых работает генерализация (logicalPixel).
  • Уровень масштабирования, при котором и выше видны только отдельные маркеры, без групп (maxZoom).
  • Уровень масштабирования, при котором и ниже перестают формироваться новые группы (minZoom).
self.objectManager = MapObjectManager.withGeneralization(
map: map,
logicalPixel: LogicalPixel(80.0),
maxZoom: Zoom(19.0),
minZoom: Zoom(8.0)
)

После создания менеджера объектов с генерализацией вы можете добавлять маркеры обычными способами через addObject() или addObjects().

Стилизация объектов

Вы можете настроить сложный стиль для динамического объекта с помощью Редактора стилей. Например, настроить максимальный и минимальный масштаб, при котором должен отображаться этот объект.

  1. Создайте стилевой слой для объекта:

    1. Откройте Редактор стилей.

    2. Откройте нужный стиль или создайте новый.

    3. В разделе Слои нажмите значок Добавить слой.

    4. Выберите тип слоя в зависимости от типа объекта. Для текущей задачи поддерживаются только типы Полигон, Линия и Точка. Подробнее о слоях см. в статье Типы слоёв для мобильного SDK.

    5. На вкладке Данные прокрутите список вниз и выберите JSON — добавить вручную.

    6. Добавьте атрибут db_sublayer с уникальным идентификатором слоя (например, my_object_layer): вставьте следующий код в текстовое поле:

      ["match", ["get", "db_sublayer"], ["my_object_layer"], true, false]

      Этот идентификатор будет далее использован для ссылки на стилевой слой из кода.

      Пример редактирования JSON
    7. Настройте другие параметры стиля в соответствующих вкладках.

    8. Экспортируйте и подключите стиль.

  2. Создайте динамический объект с помощью GeometryMapObjectBuilder и укажите идентификатор созданного слоя в методе setObjectAttribute(). Например, чтобы добавить объект в виде точки:

    let geometryObject = GeometryMapObjectBuilder()
    .setGeometry(geometry: PointGeometry(point: point)) // Геометрия в виде точки
    .setObjectAttribute(name: "db_sublayer", value: .string("my_object_layer"))
    .createObject() // Создание объекта
  3. Чтобы объект отобразился на карте, добавьте его в источник данных:

    1. Создайте источник:

      let geometrySource = GeometryMapObjectSourceBuilder(context: sdkContext).createSource()
    2. Добавьте источник на карту:

      map.addSource(source: geometrySource)
    3. Добавьте созданный объект в источник:

      geometrySource.addObject(item: geometryObject)

Выделение объектов

Настройка стилей

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

Настройки свойства выделенного объекта находятся в табе "Выделенное состояние":

Выделение объектов по нажатию на карту

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

Выделение объектов происходит с помощью вызова метода setHighlighted(), который получает на вход список идентификаторов справочника изменяемых объектов DgisObjectId. Внутри метода getRenderedObjects() мы можем получить все данные, необходимые для использования этого метода, как то источник данных объектов и их идентификаторы:

private func tap(point: ScreenPoint, tapRadius: ScreenDistance) {
let scale = UIScreen.main.nativeScale
let point = ScreenPoint(x: Float(location.x * scale), y: Float(location.y * scale))
self.getRenderedObjectsCancellable?.cancel()
let cancel = self.map.getRenderedObjects(centerPoint: point, radius: tapRadius).sink(
receiveValue: {
infos in
// Получим ближайший к месту нажатия объект внутри установленного радиуса
guard let info = infos.first(
where: {
$0.item.source is DgisSource
&& $0.item.item is DgisMapObject
}
) else { return }

// Сохраним источник данных объекта и его идентификатор
let source = info.item.source as! DgisSource
let id = (info.item.item as! DgisMapObject).id

// Выделяем объект и входы в него.
let future = searchManager.searchByDirectoryObjectId(objectId: id)

self.getDirectoryObjectCancellable = future.sinkOnMainThread(
receiveValue: {
[weak self] directoryObject in
guard let self = self else { return }
guard let directoryObject = directoryObject else { return }
guard let objectId = directoryObject.id else { return }

var selectedObjectIds = [objectId]
directoryObject.entrances.forEach { entrance in
selectedObjectIds.append(entrance.id)
}

source.setHighlighted(directoryObjectIds: selectedObjectIds, highlighted: true)
},
failure: { ... }
)
},
failure: { ... }
)
...
}

Управление камерой

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

Перелёт

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

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

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

// Новая позиция камеры.
let newCameraPosition = CameraPosition(
point: GeoPoint(latitude: 55.752425, longitude: 37.613983),
zoom: Zoom(value: 16)
)

// Запуск перелёта.
let future = map.camera.move(
position: newCameraPosition,
time: 0.4,
animationType: .linear
)

// Получение события завершения перелета.
let cancellable = future.sink { _ in
print("Перелет камеры завершён.")
} failure: { error in
print("Возникла ошибка: \(error.localizedDescription)")
}

Получение состояния камеры

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

let currentState = map.camera.state

Подписаться на изменения состояния камеры можно, используя stateChannel.sink.

// Подписка.
let connection = map.camera.stateChannel.sink { state in
print("Состояние камеры изменилось на \(state)")
}

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

Получение позиции камеры

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

let currentPosition = map.camera.position
print("Координаты: \(currentPosition.point)")
print("Приближение: \(currentPosition.zoom)")
print("Наклон: \(currentPosition.tilt)")
print("Поворот: \(currentPosition.bearing)")

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

// Подписка.
let connection = map.camera.positionChannel.sink { position in
print("Изменилась позиция камеры или угол наклона/поворота.")
}

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

Расчёт позиции камеры

Чтобы отобразить на экране объект или группу объектов, используйте метод расчёта позиции камеры calcPosition:

// Чтобы «увидеть» два маркера на карте:

// Создаём геометрию, которая охватывает оба объекта
let geometry = ComplexGeometry(geometries: [PointGeometry(point: point1), PointGeometry(point: point2)])
// Рассчитываем нужную позицию
let position = calcPosition(camera: map.camera, geometry: geometry)
// Используем рассчитанную позицию
map.camera.move(position: position)

Пример выше выдаст результат похожий на такой:

Маркеры обрезаются наполовину, так как у метода есть информация только о геометриях, а не объектах. В данном примере позиция маркера — это его центр. Метод рассчитал позицию так, чтобы вписать центры маркеров в активную область. Активная область отображается в виде красного прямоугольника по краям экрана. Чтобы отобразить маркеры целиком, вы можете задать активную область.

Например, задайте отступы слева и справа экрана:

let geometry = ComplexGeometry(geometries: [PointGeometry(point: point1), PointGeometry(point: point2)])

// Задаём отступы слева и справа, чтобы маркеры отображались полностью
map.camera.padding.left = 100
map.camera.padding.right = 100
let position = calcPosition(camera: map.camera, geometry: geometry)
map.camera.move(position: position)

Результат:

Помимо задания настроек в камере, вы можете задавать определённые параметры только для расчёта позиции. Например, указанные отступы можно задать только в методе расчёта позиции и получить такой же результат.

let geometry = ComplexGeometry(geometries: [PointGeometry(point: point1), PointGeometry(point: point2)])
// Задаём активную область только для расчёта позиции
let position = calcPosition(camera: map.camera, geometry: geometry, screenArea: Padding(left: 100, right: 100))
map.camera.move(position: position)

Результат:

На изображении видно, что активная область не изменилась, но маркеры вписаны полностью. Такой подход может приводить к неожиданному поведению, так как позиция камеры задаёт геокоординату, которая должна быть в точке позиции камеры (красный кружок в центре экрана). Настройки padding, positionPoint и size влияют на положение данной точки.

Если при вычислении позиции в метод будут переданы параметры, которые сместят точку позиции камеры, то использование результата приведёт к неожиданному поведению. Например, если вы задаёте ассиметричную активную область, то изображение может сильно смещаться.

Пример установки одной и той же позиции для разных отступов:

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

Настройка точки позиции и обзора камеры

Вы можете управлять отображением карты на экране, например, менять размер области видимости карты, при этом сохраняя позицию обзора. Для этого используйте экранные точки: точку позиции камеры BaseCamera.positionPoint и точку обзора камеры BaseCamera.viewPoint.


Точка позиции камеры (BaseCamera.positionPoint) — точка экрана, к которой привязана камера, с учётом отступов BaseCamera.padding. Точка задаётся относительно области видимости карты:

let cameraPositionPoint = CameraPositionPoint(x: 0.5, y: 0.5)
map.camera.setPositionPoint(positionPoint: Constants.cameraPositionPoint)

При изменении точки позиции камеры меняется область видимости карты и смещается точка наблюдения CameraPosition.Point — точка на местности в географических координатах, которая находится в точке позиции камеры. Угол наклона CameraPosition.Tilt и угол поворота камеры CameraPosition.Bearing не меняются:


Точка обзора камеры (BaseCamera.viewPoint) — точка экрана, в которую направлен «взгляд» камеры. Точка задаётся относительно области видимости карты:

let cameraViewPoint = CameraViewPoint(x: 0.5, y: 0.5)
map.camera.setViewPoint(viewPoint: Constants.cameraViewPoint)

При изменении точки обзора камеры меняется направление обзора относительно точки наблюдения. Точка наблюдения CameraPosition.Point не смещается, также не меняется угол наклона CameraPosition.Tilt и угол поворота камеры CameraPosition.Bearing:

visibleArea и visibleRect

У камеры есть два свойства, которые описывают геометрию видимой области, но делают это по-разному. visibleRect имеет тип GeoRect и всегда является прямоугольником. visibleArea представляет из себя произвольную геометрию. Проще всего увидеть на примере с разными углами наклона камеры относительно карты:

  • При наклоне 45° visibleRect и visibleArea не будут равны: visibleRect, в данном случае, будет больше, т.к. он должен быть прямоугольником и должен содержать в себе visibleArea

    Синим цветом изображена visibleArea, красным – visibleRect

  • При наклоне 0° visibleArea и visibleRect будут совпадать, как видно из изменения цвета.

Определение попадания объекта в видимую область камеры

С помощью свойства visibleArea мы можем получить область карты, которая попадает в камеру, в виде Geometry. С помощью метода intersects() мы можем получить пересечение видимой области камеры с нужной нам геометрией:

//Допустим, у нас есть маркер и нужно узнать, попадает ли он в видимую область карты
// let marker: Marker
let markerGeometry: Geometry = PointGeometry(point: marker.position)

let intersects: Bool = map.camera.visibleArea.intersects(geometry: markerGeometry)

Пробки на карте

Чтобы отобразить слой пробок на карте, создайте источник данных TrafficSource и передайте его в метод карты addSource().

let trafficSource = TrafficSource(context: sdk.context)
map.addSource(source: trafficSource)

Дорожные события на карте

Вы можете настроить отображение на карте дорожных событий из данных 2ГИС, а также добавить собственные события.

Отображение событий на карте

Чтобы показать слой дорожных событий на карте, создайте источник данных RoadEventSource и добавьте его на карту с помощью метода карты addSource():

let roadEventSource = RoadEventSource(context: sdk.context)
map.addSource(source: roadEventSource)

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

map.removeSource(source: roadEventSource)

Добавление события

Вы можете добавить собственное дорожное событие на карту, которое смогут увидеть все пользователи карт 2ГИС.

Примечание

Вы можете разместить событие на карте только в радиусе 2 км от своего текущего местоположения.

  1. Создайте экземпляр менеджера объектов RoadEventManager:

    self.roadEventManager = RoadEventManager(context: sdk.context)
  2. Добавьте событие одного из типов ниже (каждому типу соответствует своя иконка на карте):

    • ДТП. Вызовите метод createAccident() и укажите координаты события (GeoPoint), затронутые полосы движения (Lane) и текстовое описание события:

      let accidentLocation = GeoPoint(latitude: 55.751244, longitude: 37.618423)
      let accidentLanes: LaneOptionSet = [.left, .center] // затронуты левая и средняя полосы движения

      let accidentFuture = roadEventManager.createAccident(
      location: accidentLocation,
      lanes: accidentLanes,
      description: "ДТП на левой полосе"
      )

      // Обработка результата
      let accidentCancellable = accidentFuture.sink(
      receiveValue: { result in
      switch result {
      case .event(let event):
      print("Событие ДТП успешно создано: \(event.id)")
      case .error(let error):
      print("Ошибка при создании события: \(error)")
      }
      },
      failure: { error in
      print("Произошла ошибка: \(error)")
      }
      )
    • Камера ГИБДД. Вызовите метод createCamera() и укажите координаты события (GeoPoint) и текстовое описание:

      let cameraLocation = GeoPoint(latitude: 55.752220, longitude: 37.615560)

      let cameraFuture = roadEventManager.createCamera(
      location: cameraLocation,
      description: "Камера контроля скорости"
      )

      // Обработка результата
      let cameraCancellable = cameraFuture.sink(
      receiveValue: { result in
      switch result {
      case .event(let event):
      print("Событие камеры успешно создано: \(event.id)")
      case .error(let error):
      print("Ошибка при создании события: \(error)")
      }
      },
      failure: { error in
      print("Произошла ошибка: \(error)")
      }
      )
    • Перекрытие дорожного движения. Вызовите метод createRoadRestriction() и укажите координаты события (GeoPoint) и текстовое описание:

      let restrictionLocation = GeoPoint(latitude: 55.753930, longitude: 37.620795)

      let restrictionFuture = roadEventManager.createRoadRestriction(
      location: restrictionLocation,
      description: "Дорога перекрыта для фестиваля"
      )

      // Обработка результата
      let restrictionCancellable = restrictionFuture.sink(
      receiveValue: { result in
      switch result {
      case .event(let event):
      print("Событие перекрытия успешно создано: \(event.id)")
      case .error(let error):
      print("Ошибка при создании события: \(error)")
      }
      },
      failure: { error in
      print("Произошла ошибка: \(error)")
      }
      )
    • Дорожные работы. Вызовите метод createRoadWorks() и укажите координаты события (GeoPoint), затронутые полосы движения (Lane) и текстовое описание события:

      let roadWorksLocation = GeoPoint(latitude: 55.754800, longitude: 37.621000)
      let roadWorksLanes: LaneOptionSet = [.right] // затронута правая полоса движения

      let roadWorksFuture = roadEventManager.createRoadWorks(
      location: roadWorksLocation,
      lanes: roadWorksLanes,
      description: "Ремонт дорожного покрытия"
      )

      // Обработка результата
      let roadWorksCancellable = roadWorksFuture.sink(
      receiveValue: { result in
      switch result {
      case .event(let event):
      print("Событие дорожных работ успешно создано: \(event.id)")
      case .error(let error):
      print("Ошибка при создании события: \(error)")
      }
      },
      failure: { error in
      print("Произошла ошибка: \(error)")
      }
      )
    • Комментарий. Вызовите метод createComment() и укажите координаты события (GeoPoint) и текстовое описание:

      let commentLocation = GeoPoint(latitude: 55.755000, longitude: 37.622000)

      let commentFuture = roadEventManager.createComment(
      location: commentLocation,
      description: "Осторожно, гололёд!"
      )

      // Обработка результата
      let commentCancellable = commentFuture.sink(
      receiveValue: { result in
      switch result {
      case .event(let event):
      print("Комментарий успешно создан: \(event.id)")
      case .error(let error):
      print("Ошибка при создании комментария: \(error)")
      }
      },
      failure: { error in
      print("Произошла ошибка: \(error)")
      }
      )
    • Другое событие. Вызовите метод createOther() и укажите координаты события (GeoPoint), затронутые полосы движения (Lane) и текстовое описание события:

      let otherLocation = GeoPoint(latitude: 55.756000, longitude: 37.623000)
      let otherLanes: LaneOptionSet = [.center, .right] // затронуты правая и средняя полосы движения

      let otherFuture = roadEventManager.createOther(
      location: otherLocation,
      lanes: otherLanes,
      description: "Препятствие на дороге"
      )

      // Обработка результата
      let otherCancellable = otherFuture.sink(
      receiveValue: { result in
      switch result {
      case .event(let event):
      print("Другое событие успешно создано: \(event.id)")
      case .error(let error):
      print("Ошибка при создании события: \(error)")
      }
      },
      failure: { error in
      print("Произошла ошибка: \(error)")
      }
      )

Получение объектов по экранным координатам

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

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

private func tap(location: CGPoint) {
let scale = UIScreen.main.nativeScale
let point = ScreenPoint(x: Float(location.x * scale), y: Float(location.y * scale))
let radius = ScreenDistance(30.0)
self.getRenderedObjectsCancellable?.cancel()
let cancel = map.getRenderedObjects(centerPoint: point, radius: radius).sink(
receiveValue: {
infos in
// Первый объект в массиве - самый близкий к координатам.
guard let info = infos.first else { return }
// Обработка результата в главной очереди.
DispatchQueue.main.async {
[weak self] in
self?.handle(selectedObject: info)
}
},
failure: { error in
print("Ошибка получения информации об объектах: \(error)")
}
)
// Сохраняем результат вызова, так как его удаление отменяет обработку.
self.getRenderedObjectsCancellable = cancel
}

Также можно установить коллбэк MapObjectTappedCallback для тапа или лонгтапа в IMapView с помощью методов addObjectTappedCallback и addObjectLongPressCallback. В этот коллбэк придет RenderedObjectInfo для объекта, который находится ближе всего к точке касания.

...
let mapObjectTappedOrLongPress = MapObjectTappedCallback(callback: { [weak self] objectInfo in
print("Произвольные данные объекта: \(objectInfo.item.item.userData)")
})
...

self.mapView.addObjectTappedCallback(callback: mapObjectTappedOrLongPress)
self.mapView.addObjectLongPressCallback(callback: mapObjectTappedOrLongPress)

Распознаватель жестов карты

Для кастомизации распознавателя жестов карты, необходимо задать реализацию протокола IMapGestureView в IMapView или реализацию IMapGestureViewFactory в MapOptions. Если ни одна из этих имплементаций задана не будет, то будет использована реализация по умолчанию. Пример такой кастомизации распознавателя можно посмотреть здесь.

Размещение View на карте

IMarkerViewFactory – фабрика для создания View, привязанных к геокоординатам, находится в объекте Container. Попробуем создать IMarkerView с помощью этой фабрики:

let sdk: Container
// Любой View, для примера возьмем UILabel
let view: UILabel
// Позиция на карте, к которой прикрепится view
let position: GeoPointWithElevation
// Точка внутри view, к которой будет привязана координата position
let anchor: Anchor = Anchor()
// Смещение в пикселях по осям
let offsetX: CGFloat = 0.0
let offsetY: CGFloat = 0.0


sdk.markerViewFactory.make(
view: view,
position : position,
anchor: anchor,
offsetX: offsetX,
offsetY: offsetY
)

Для отображения полученного View, необходимо добавить его методом add() объекта IMarkerViewOverlay, полученного из фабрики IMapFactory.