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

Карта

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

class SimpleMapScreen extends StatelessWidget {
const SimpleMapScreen({super.key});

@override
Widget build(BuildContext context) {
final sdkContext = sdk.DGis.initialize();

return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: sdk.MapWidget(
sdkContext: sdkContext,
mapOptions: sdk.MapOptions(),
),
);
}
}

Основным объектом для управления картой является MapWidgetController. Внутри MapWidget он будет приватным, поэтому для получения доступа к нему рекомендуется создать его вручную и передать в MapWidget параметром:

class SimpleMapScreen extends StatelessWidget {
const SimpleMapScreen({super.key});

@override
Widget build(BuildContext context) {
final sdk.Context sdkContext = sdk.DGis.initialize();
final sdk.MapWidgetController mapWidgetController = sdk.MapWidgetController();

return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: sdk.MapWidget(
sdkContext: sdkContext,
mapOptions: sdk.MapOptions(),
controller: mapWidgetController,
),
);
}
}

Объект карты (Map) можно получить, вызвав метод getMapAsync(). Этот метод работает асинхронно, поэтому его безопасно вызывать в любой момент жизненного цикла, начиная с initState().

Другие действия с картой можно выполнить внутри метода getMapAsync(), т. к. это первый момент, когда карта становится доступна. Все операции с картой, которые описаны в этом разделе далее, подразумевают, что карта уже проинициализирована, а все вызовы происходят внутри метода.

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

sdk.Map? sdkMap = null;

@override
initState() {
super.initState();
mapWidgetController.getMapAsync((map) {
sdkMap = map;
});
}

Источники данных для карты

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

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

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

// Создание источника данных
final sdk.MyLocationMapObjectSource locationSource = sdk.MyLocationMapObjectSource(sdkContext);

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

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

map?.removeSource(locationSource);

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

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

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

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

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

    • OFFLINE — чтобы всегда использовать только предварительно загруженные данные.
    • HYBRID_ONLINE_FIRST — чтобы преимущественно использовать онлайн-данные с серверов 2ГИС. Предзагруженные данные используются, только когда они совпадают с онлайн-данными или когда данные с серверов получить невозможно.
    • HYBRID_OFFLINE_FIRST — чтобы преимущественно использовать предзагруженные данные. Онлайн-данные с серверов 2ГИС используются, только когда отсутствуют предзагруженные данные.
    final dgisSource = sdk.DgisSource.createDgisSource(
    sdkContext,
    sdk.DgisSourceWorkingMode.hybridOfflineFirst,
    );
  3. При создании карты укажите созданный источник в MapOptions:

    final  mapOptions = sdk.MapOptions(sources: [dgisSource]);

    // Теперь MapOptions можно передать в MapWidget
    sdk.MapWidget(
    sdkContext: sdkContext,
    mapOptions: mapOptions,
    controller: mapWidgetController,
    );

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.MapObjectManager mapObjectManager = sdk.MapObjectManager(map)

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

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

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

Маркер

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

Единственный обязательный параметр — координаты маркера (position).

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.Marker marker = sdk.Marker(
sdk.MarkerOptions(
position: sdk.GeoPointWithElevation(
latitude: sdk.Latitude(55.752425),
longitude: sdk.Longitude(37.613983),
)
),
);
mapObjectManager.addObject(marker);

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.ImageLoader loader = sdk.ImageLoader(_sdkContext);
final sdk.Image icon = loader.loadSVGFromAsset("assets/icons/bridge.svg");

final marker = sdk.Marker(
sdk.MarkerOptions(
position: sdk.GeoPointWithElevation(
latitude: sdk.Latitude(55.752425),
longitude: sdk.Longitude(37.613983),
icon = icon
)
),
);

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

// Изменение позиции маркера
marker.position = const sdk.GeoPointWithElevation(
latitude: sdk.Latitude(55.74460317215391),
longitude: sdk.Longitude(37.63435363769531),
);

// Изменение иконки
late sdk.ImageLoader loader;
marker.icon = loader.loadSVGFromAsset("Path to SVG");

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

// Изменение прозрачности иконки
marker.iconOpacity = const sdk.Opacity(0.5);

// Изменение подписи маркера
marker.text = 'Text';

// Изменения стиля подписи
marker.textStyle = sdk.TextStyle();

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

// Изменение целевой ширины маркера
marker.iconWidth = const sdk.LogicalPixel(10);

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

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

Линия

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

// Координаты вершин ломаной линии
final sdk.GeoPoint point1 = sdk.GeoPoint(latitude: sdk.Latitude(55.7), longitude: sdk.Longitude(37.6));
final sdk.GeoPoint point2 = sdk.GeoPoint(latitude: sdk.Latitude(55.8), longitude: sdk.Longitude(37.7));
final sdk.GeoPoint point3 = sdk.GeoPoint(latitude: sdk.Latitude(55.9), longitude: sdk.Longitude(37.8));
final sdk.GeoPoint geopoints = <sdk.GeoPoint>[point1,point2,point3]

// Создание линии
final sdk.Polyline polyline = sdk.Polyline(
sdk.PolylineOptions(
points: geopoints,
width: sdk.LogicalPixel(2),
),
);

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

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

// Изменение координат вершин линии
polyline.points = geopoints;

// Изменение толщины линии
polyline.width = sdk.LogicalPixel(10);

// Изменение цвета линии
polyline.color = sdk.Color(Colors.black.value);

// Изменение стёртой части
polyline.erasedPart = 0.1;

// Изменение параметров пунктирной линии
polyline.dashedPolylineOptions = sdk.DashedPolylineOptions();

// Изменение параметров градиентной линии
polyline.gradientPolylineOptions = sdk.GradientPolylineOptions();

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

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.GeoPoint point1 = sdk.GeoPoint(latitude: sdk.Latitude(55.7), longitude: sdk.Longitude(37.6));
final sdk.GeoPoint point2 = sdk.GeoPoint(latitude: sdk.Latitude(55.8), longitude: sdk.Longitude(37.7));
final sdk.GeoPoint point3 = sdk.GeoPoint(latitude: sdk.Latitude(55.9), longitude: sdk.Longitude(37.8));
final sdk.GeoPoint countor = <sdk.GeoPoint>[point1,point2,point3];
final sdk.GeoPoint point4 = sdk.GeoPoint(latitude: sdk.Latitude(55.7), longitude: sdk.Longitude(37.6));
final sdk.GeoPoint point5 = sdk.GeoPoint(latitude: sdk.Latitude(55.8), longitude: sdk.Longitude(37.7));
final sdk.GeoPoint point6 = sdk.GeoPoint(latitude: sdk.Latitude(55.9), longitude: sdk.Longitude(37.8));
final sdk.GeoPoint points = <sdk.GeoPoint>[point4,point5,point6];

final sdk.Polygon polygon = sdk.Polygon(
sdk.PolygonOptions(
contours: [countor,points],
strokeWidth: sdk.LogicalPixel(5),
),
);
mapObjectManager.addObject(polygon);

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

// Изменение вершин многоугольника
polygon.contours = [contuor, points];

// Изменение цвета заливки многоугольника
polygon.color = sdk.Color(Colors.black.value);

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

// Изменение цвета границы многоугольника
polygon.strokeColor = sdk.Color(Colors.black.value);

Окружность

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

// Настройки окружности
final circle = sdk.Circle(
const sdk.CircleOptions(
position: sdk.GeoPoint(latitude: sdk.Latitude(10),longitude: sdk.Longitude(10)),
radius: sdk.Meter(10)
)
);

// Создание и добавление окружности
mapObjectManager.addObject(circle);

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

// Изменение центра окружности
circle.position = sdk.GeoPoint(latitude: sdk.Latitude(55.752425),longitude: sdk.Longitude(37.613983));

// Изменение радиуса окружности
circle.radius = sdk.Meter(10);

// Изменение цвета заливки окружности
circle.color = sdk.Color(Colors.black.value);

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

// Изменение цвета границы окружности
circle.strokeColor = sdk.Color(Colors.black.value);

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

// Подготавливаем коллекцию объектов
final List<sdk.Marker> markers = [];
final sdk.MarkerOptions markerOptions = sdk.MarkerOptions(
<params>
);

markers.add(sdk.Marker(markerOptions));

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

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

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

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

  • Экземпляр карты (map).
  • Минимальное расстояние между маркерами в логических пикселях на уровнях масштабирования, при которых работает кластеризация (logicalPixel).
  • Уровень масштабирования, при котором и выше видны только отдельные маркеры, без кластеров (maxZoom).
  • Уровень масштабирования, при котором и ниже перестают формироваться новые кластеры (minZoom).
  • Пользовательскую реализацию протокола SimpleClusterRenderer, который используется для кастомизации кластеров в MapObjectManager.
import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

class SimpleClusterRendererImpl implements sdk.SimpleClusterRenderer {
final sdk.Image image;
int idx = 0;

SimpleClusterRendererImpl({
required this.image,
});

@override
sdk.SimpleClusterOptions renderCluster(sdk.SimpleClusterObject cluster) {
final int objectCount = cluster.objectCount;
final sdk.MapDirection? iconMapDirection =
objectCount < 5 ? const sdk.MapDirection(45) : null;
idx += 1;

const double baseSize = 30.0;
final double sizeMultiplier = 1.0 + (objectCount / 50.0);
final double iconSize = min(baseSize * sizeMultiplier, 100);

return sdk.SimpleClusterOptions(
icon: image,
iconMapDirection: iconMapDirection,
text: objectCount.toString(),
iconWidth: sdk.LogicalPixel(iconSize),
userData: idx,
zIndex: const sdk.ZIndex(1),
);
}
}

clusterRenderer ??= SimpleClusterRendererImpl(
image: loader.loadSVGFromAsset("assets/icons/bridge.svg"),
);

mapObjectManager = sdk.MapObjectManager.withClustering(
map,
const sdk.LogicalPixel(80.0),
const sdk.Zoom(19.0),
const sdk.Zoom(8.0),
clusterRenderer
);

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

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

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

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

  • Экземпляр карты (map).
  • Минимальное расстояние между маркерами в логических пикселях на уровнях масштабирования, при которых работает генерализация (logicalPixel).
  • Уровень масштабирования, при котором и выше видны только отдельные маркеры, без групп (maxZoom).
  • Уровень масштабирования, при котором и ниже перестают формироваться новые группы (minZoom).
import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

mapObjectManager = sdk.MapObjectManager.withGeneralization(
map,
const sdk.LogicalPixel(80.0),
const sdk.Zoom(19.0),
const sdk.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(). Например, чтобы добавить объект в виде точки:

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

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

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

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

      geometrySource.addObject(geometryObject);

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

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

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

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

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

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

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

mapWidgetController
.addObjectTappedCallback((sdk.RenderedObjectInfo info) async {
// Получим ближайший к месту нажатия объект внутри установленного радиуса
final sdk.RenderedObject obj = info.item;

// В этом примере мы хотим найти информацию о выбранном объекте в справочнике.
// Для этого мы должны убедиться, что тип этого объекта может быть найден
if (obj.source is sdk.DgisSource && obj.item is sdk.DgisMapObject) {
final source = obj.source as sdk.DgisSource;

// Произведем поиск
final foundObject =
await sdk.SearchManager.createOnlineManager(sdkContext)
.searchByDirectoryObjectId((obj.item as sdk.DgisMapObject).id)
.value;

final entranceIds =
foundObject?.entrances.map((entrance) => entrance.id).toList() ??
List.empty();

// Снимаем выделение с выбранных ранее объектов
source
..setHighlighted(source.highlightedObjects, false)
// Выделяем полученный объект и входы
..setHighlighted(entranceIds, true);
}
});

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

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

Перелёт

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

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.Map map = mapWidgetController.getMapAsync((map) {
sdkMap = map;
});
final sdk.CameraPosition cameraPosition = const sdk.CameraPosition(
point: sdk.GeoPoint(
latitude: sdk.Latitude(55.759909),
longitude: sdk.Longitude(37.618806),
),
zoom: sdk.Zoom(15),
tilt: sdk.Tilt(15),
bearing: sdk.Bearing(115),
);

map.camera.moveToCameraPosition(
position,
const Duration(seconds: 3),
sdk.CameraAnimationType.linear,
);

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

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.CameraState currentState = map.camera.state;

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

map.camera.stateChannel.listen((sdk.CameraState cameraState) {
// Обработка изменений состояния камеры
});

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.CameraPosition currentPosition = sdkMap.camera.position;

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

sdkMap.camera.positionChannel.listen((position) {
// Обработка изменений позиции камеры
});

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

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

// Создаём геометрию, которая охватывает оба объекта
final List<sdk.SimpleMapObject> objects = [marker1, marker2, marker3];
// Рассчитываем нужную позицию
final position = sdk.calcPositionForObjects(camera, objects, styleZoomToTiltRelation, screenArea, tilt, bearing, size)

// Используем рассчитанную позицию
map.camera.moveToCameraPosition(position)

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

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

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

// Задаём отступы сверху и снизу, чтобы маркеры отображались полностью
sdkCamera.padding = sdk.Padding(top: 100, bottom: 100);

final position = sdk.calcPositionForObjects(camera, objects, styleZoomToTiltRelation, screenArea, tilt, bearing, size);
map.camera.moveToCameraPosition(position);

Результат:

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

final sdk.Geometry geometry = sdk.ComplexGeometry([sdk.PointGeometry(point1), sdk.PointGeometry(point2),])
// Задаём активную область только для расчёта позиции
val position = sdk.calcPositionForGeometry(map.camera, geometry, styleZoomToTiltRelation, sdk.Padding({top:100, bottom:100}), tilt, bearing, size,)
map.camera.move(position)

Результат:

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

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

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

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

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

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


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

const cameraPositionPoint = sdk.CameraPositionPoint(x: 0.5, y: 0.5);
map.camera.positionPoint = cameraPositionPoint;

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


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

const cameraViewPoint = sdk.CameraViewPoint(x: 0.5, y: 0.5);
map.camera.viewPoint = 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() мы можем получить пересечение видимой области камеры с нужной нам геометрией:

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

camera.visibleArea.intersects(geometry)

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

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

final sdk.TrafficSource trafficSource = sdk.TrafficSource(sdkContext);
map.addSource(trafficSource);

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

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

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

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

final roadEventSource = sdk.RoadEventSource(sdkContext);
map?.addSource(roadEventSource);

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

map?.removeSource(roadEventSource);

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

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

Примечание

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

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

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

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

      final accidentLocation = sdk.GeoPoint(
      latitude: sdk.Latitude(55.751244),
      longitude: sdk.Longitude(37.618423)
      );
      final accidentLanes = sdk.LaneEnumSet.of([sdk.Lane.left, sdk.Lane.center]); // затронуты левая и средняя полосы движения

      final accidentOperation = roadEventManager.createAccident(
      accidentLocation,
      accidentLanes,
      "ДТП на левой полосе"
      );

      // Обработка результата
      accidentOperation.value.then((result) {
      if (result.isEvent) {
      print("Событие ДТП успешно создано! ID: ${result.asEvent?.id}");
      } else if (result.isError) {
      print("Ошибка создания: ${result.asError}");
      }
      });
    • Камера ГИБДД. Вызовите метод createCamera() и укажите координаты события (GeoPoint) и текстовое описание:

      final cameraLocation = sdk.GeoPoint(
      latitude: sdk.Latitude(55.752220),
      longitude: sdk.Longitude(37.615560)
      );

      final cameraOperation = roadEventManager.createCamera(
      cameraLocation,
      "Камера контроля скорости"
      );

      // Обработка результата
      cameraOperation.value.then((result) {
      if (result.isEvent) {
      print("Событие камеры успешно создано! ID: ${result.asEvent?.id}");
      } else if (result.isError) {
      print("Ошибка создания: ${result.asError}");
      }
      });
    • Перекрытие дорожного движения. Вызовите метод createRoadRestriction() и укажите координаты события (GeoPoint) и текстовое описание:

      final restrictionLocation = sdk.GeoPoint(
      latitude: sdk.Latitude(55.753930),
      longitude: sdk.Longitude(37.620795)
      );

      final restrictionOperation = roadEventManager.createRoadRestriction(
      restrictionLocation,
      "Дорога перекрыта для фестиваля"
      );

      // Обработка результата
      restrictionOperation.value.then((result) {
      if (result.isEvent) {
      print("Событие перекрытия успешно создано! ID: ${result.asEvent?.id}");
      } else if (result.isError) {
      print("Ошибка создания: ${result.asError}");
      }
      });
    • Дорожные работы. Вызовите метод createRoadWorks() и укажите координаты события (GeoPoint), затронутые полосы движения (Lane) и текстовое описание события:

      final roadWorksLocation = sdk.GeoPoint(
      latitude: sdk.Latitude(55.754800),
      longitude: sdk.Longitude(37.621000)
      );
      final roadWorksLanes = sdk.LaneEnumSet.of([sdk.Lane.right]); // затронута правая полоса движения

      final roadWorksOperation = roadEventManager.createRoadWorks(
      roadWorksLocation,
      roadWorksLanes,
      "Ремонт дорожного покрытия"
      );

      // Обработка результата
      roadWorksOperation.value.then((result) {
      if (result.isEvent) {
      print("Событие дорожных работ успешно создано! ID: ${result.asEvent?.id}");
      } else if (result.isError) {
      print("Ошибка создания: ${result.asError}");
      }
      });
    • Комментарий. Вызовите метод createComment() и укажите координаты события (GeoPoint) и текстовое описание:

      final commentLocation = sdk.GeoPoint(
      latitude: sdk.Latitude(55.755000),
      longitude: sdk.Longitude(37.622000)
      );

      final commentOperation = roadEventManager.createComment(
      commentLocation,
      "Осторожно, гололёд!"
      );

      // Обработка результата
      commentOperation.value.then((result) {
      if (result.isEvent) {
      print("Комментарий успешно создан! ID: ${result.asEvent?.id}");
      } else if (result.isError) {
      print("Ошибка создания: ${result.asError}");
      }
      });
    • Другое событие. Вызовите метод createOther() и укажите координаты события (GeoPoint), затронутые полосы движения (Lane) и текстовое описание события:

      final otherLocation = sdk.GeoPoint(
      latitude: sdk.Latitude(55.756000),
      longitude: sdk.Longitude(37.623000)
      );
      final otherLanes = sdk.LaneEnumSet.of([sdk.Lane.center, sdk.Lane.right]); // затронуты правая и средняя полосы движения

      final otherOperation = roadEventManager.createOther(
      otherLocation,
      otherLanes,
      "Препятствие на дороге"
      );

      // Обработка результата
      otherOperation.value.then((result) {
      if (result.isEvent) {
      print("Другое событие успешно создано! ID: ${result.asEvent?.id}");
      } else if (result.isError) {
      print("Ошибка создания: ${result.asError}");
      }
      });

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

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

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

import 'package:dgis_mobile_sdk_map/dgis.dart' as sdk;

map.getRenderedObjects(sdk.ScreenPoint(), sdk.ScreenDistance(30.0)).then((List<sdk.RenderedObjectInfo> info) {
// Обработка
});

Кроме использования напрямую, можно установить коллбэк для тапа (addObjectTappedCallback) или лонгтапа (addObjectLongTouchCallback) в MapWidgetController, как показано в примере с выделением объектов по нажатию на карту.

Работа с жестами управления картой

Кастомизировать работу с жестами можно двумя способами:

  • Настроить существующие жесты.
  • Реализовать собственный механизм распознавания жестов.

Настройка жестов

Из коробки управлять картой можно при помощи следующих жестов:

  • сдвиг карты в любом направлении одним пальцем;
  • сдвиг карты в любом направлении несколькими пальцами;
  • вращение карты двумя пальцами;
  • масштабирование карты двумя пальцами (щипок);
  • приближение карты двойным тапом;
  • отдаление карты тапом двумя пальцами;
  • масштабирование карты при помощи тап-тап-свайп жеста одним пальцем;
  • наклон камеры свайпом двумя пальцами вверх или вниз.

По умолчанию включены все жесты. При необходимости можно отключить какие-либо жесты при помощи GestureManager.

Получить GestureManager можно напрямую у MapWidgetController:

mapWidgetController.getMapAsync((map) {
gestureManager = mapWidgetController.gestureManager;
})

Для жестов доступны методы:

  • enableGesture — включение жестов;
  • disableGesture — отключение жестов;
  • gestureEnabled — проверка, включен или отключен жест.

Для изменения настроек или получения информации о нескольких жестах за раз можно работать напрямую со свойством enabledGestures.

Конкретный жест задаётся при помощи значений из Gesture. Значение scaling отвечает за всю группу жестов масштабирования карты. Отключить эти жесты по одному нельзя.

У некоторых жестов есть свой перечень настроек:

  • MultiTouchShiftSettings для сдвига несколькими пальцами;
  • RotationSettings для вращения;
  • ScalingSettings для масштабирования;
  • TiltSettings для наклона.

Подробнее о настройках можно прочитать на соответствующих страницах в документации. Объекты этих настроек доступны через свойства GestureManager.

Кроме настроек конкретного жеста, также существуют настройки поведения масштабирования и поворота карты. Можно настроить точку, относительно которой будут происходить операции поворота и масштабирования карты. По умолчанию эти операции работают относительно «центра масс» точек постановки пальцев. Это поведение можно сменить при помощи настройки EventsProcessingSettings. Настройку можно установить при помощи метода setSettingsAboutMapPositionPoint().

Для управления одновременным срабатыванием нескольких жестов используется метод setMutuallyExclusiveGestures.