Карты на Android: единый API и картографические провайдеры

NodaLogic предоставляет единый набор инструментов для работы с картами в Android-клиенте. Прикладной обработчик использует функции MapCenter, MapPoint, MapPolyline, MapRoute и другие Map*-функции, не обращаясь напрямую к классам картографического SDK.

Конкретный картографический движок выбирается элементом динамической разметки. Например, YandexMap создаёт карту на основе Yandex MapKit. Команды направляются нужному экземпляру карты по его id.

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

Примечание

Описанный здесь встроенный картографический слой предназначен для Android-клиента. Для веб-интерфейса карта может быть реализована отдельно средствами HTML и JavaScript. Тип YandexMap не является компонентом веб-разметки.

Архитектура

Работа с картами состоит из трёх уровней:

  1. Решение NodaLogic хранит координаты, точки, зоны и полученные извне маршруты как обычные бизнес-данные.

  2. Единый картографический API принимает команды Map*, направляет их карте с заданным id и передаёт события обратно в обработчики решения.

  3. Провайдер отображает карту и выполняет поддерживаемые им операции: управление камерой, рисование объектов, поиск или построение маршрута.

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

Возможности по провайдерам

Таблица относится к функциям, описанным в этой статье. При добавлении новых провайдеров в неё могут появиться дополнительные колонки.

Поддержка функций

Функция единого API

YandexMap

Отображение карты и управление камерой

Да; Yandex MapKit.

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

Да, при наличии разрешения Android и доступного местоположения.

Точки, SVG-маркеры и подписи

Да.

Полилинии, полигоны, окружности

Да.

Готовый маршрут от сервера как MapPolyline

Да; маршрутный сервис провайдера не требуется.

Автоматический расчёт автомобильного маршрута, MapRoute

Да; асинхронно, с учётом условий работы MapKit.

Поиск и обратное геокодирование

Да; асинхронно, через MapKit.

События нажатий, результатов поиска и маршрутизации

Да.

Офлайновое отображение предварительно загруженного региона

Да, при наличии соответствующей платной лицензии MapKit.

Управление офлайновыми регионами

Через менеджер карт Android-клиента и механизм MapKit.

Офлайновый поиск и автомобильная маршрутизация

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

Примечание

Таблица различает функции единого API NodaLogic и возможности SDK провайдера. Например, сам факт поддержки офлайнового поиска в MapKit не гарантирует, что любой запрос конкретного решения будет обработан локально. Для MapReverseGeocode не следует обещать офлайновую работу без отдельной проверки.

Общий Python API

Функции импортируются из nodesclient в Android-обработчике:

from nodesclient import (
    MapCall, MapState, MapCamera, MapCenter,
    MapPoint, MapPolyline, MapPolygon, MapCircle,
    MapRemove, MapClear, MapRoute,
    MapSearch, MapReverseGeocode,
    MapUserLocation, MapCenterGPS, MapFit,
)

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

Для редких действий и параметров существует универсальный низкоуровневый вызов:

MapCall("map", "action", {...})

Наличие универсального вызова не делает произвольное действие доступным на любом провайдере. В обычном коде предпочтительны готовые Map*-функции.

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

MapCenter принимает координаты из любого источника: поля узла, внешней системы, результатов поиска или GPS.

MapCenter("map", [55.75, 37.62], zoom=14)

MapCenter(
    "map",
    {"latitude": 55.75, "longitude": 37.62},
)

Если zoom не передан, текущий масштаб сохраняется.

Для дополнительных параметров камеры:

MapCamera(
    "map", 55.75, 37.62,
    zoom=15, azimuth=0, tilt=0, animated=True,
)

Показ и центрирование по местоположению самого Android-устройства:

MapUserLocation("map", visible=True)
MapCenterGPS("map", zoom=16)

Если разрешение ещё не выдано, картографический компонент запускает штатный Android-запрос разрешения. Не нужно дублировать этот запрос в прикладном обработчике. Координаты, ранее полученные из внешней системы, следует передавать через MapCenter, а не через MapCenterGPS.

Объекты на карте

Точка и её идентификатор:

MapPoint(
    "map", "client_1", 55.75, 37.62,
    text="Клиент", color="#3f7353",
)

Первый аргумент — id карты, второй — идентификатор объекта. Повторный вызов с тем же идентификатором заменяет точку.

Можно использовать собственный SVG-маркер:

FLAG_SVG = '<svg ...>...</svg>'

MapPoint(
    "map", "warehouse_1", 55.76, 37.64,
    text="Склад 1", svg=FLAG_SVG,
    svg_size=42, svg_color="#1E88E5", number="1",
    text_placement="bottom", text_offset=5,
)

Дополнительные параметры включают size, draggable, z, number_background, number_color, text_size и text_offset_from_icon. Фактическое оформление зависит от реализации маркеров у выбранного провайдера.

Удаление объекта:

MapRemove("map", "warehouse_1")

Готовый маршрут или трек

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

external_route = [
    [55.7510, 37.6100],
    [55.7542, 37.6160],
    [55.7580, 37.6210],
]

MapPolyline(
    "map", "external_route", external_route,
    color="#1565C0", width=7,
)

Две точки образуют обычный отрезок:

MapPolyline(
    "map", "segment_1",
    [[55.75, 37.61], [55.76, 37.64]],
    color="#db3030", width=5,
)

Для пунктирной линии доступны дополнительные параметры dash_length и gap_length. Передавайте точки в порядке следования по маршруту; простая полилиния не умеет находить дороги между произвольными точками самостоятельно.

Полигоны и окружности

MapPolygon(
    "map", "zone_1",
    [[55.75, 37.62], [55.75, 37.64], [55.77, 37.64]],
    stroke="#3f7353", fill="#403f7353", width=3,
)

MapCircle(
    "map", "radius_1", 55.75, 37.62, 250,
    stroke="#1565C0", fill="#401565C0", width=3,
)

События карты

При events=true последний результат записывается в:

self._data["map_event"]
self._data["<map_id>_event"]

Например, у карты delivery_map второе поле называется delivery_map_event. Если на экране несколько карт и важно исключить путаницу между ними, используйте поле с конкретным id.

Карта вызывает стандартное событие onInput; listener равен id карты. Для элемента с id="map" задайте обработчик:

{
  "event": "onInput",
  "listener": "map",
  "actions": [
    {
      "action": "run",
      "source": "internal",
      "server": "internal",
      "method": "MapEvent",
      "order": 1
    }
  ]
}

Пример Python-обработчика:

def MapEvent(self, input_data=None):
    event = self._data.get("map_event") or {}
    kind = event.get("event")
    data = event.get("data") or {}

    if kind == "tap":
        lat = data.get("latitude")
        lon = data.get("longitude")

    elif kind == "object_tap":
        object_id = data.get("object_id")
        lat = data.get("latitude")
        lon = data.get("longitude")

    elif kind == "search_ready":
        results = data.get("results") or []

    elif kind == "route_ready":
        route_count = data.get("count")

    elif kind in ("route_error", "search_error", "location_error"):
        error = data.get("error")

    return True, self._data

Возможные события: tap, long_tap, object_tap, route_ready, route_error, search_ready, search_error, location_permission_required, location_permission_granted, location_permission_denied и location_error. Некоторые из них возникают только у провайдера, реализующего соответствующую функцию.

Настройка в onShow

Карту удобно описать декларативно, а объекты добавить в onShow:

def InitMap(self, input_data=None):
    MapCenter("map", [55.80, 37.59], zoom=10, animated=False)
    MapUserLocation("map", visible=True)
    MapPoint("map", "office", 55.75, 37.62, "Офис")
    return True, self._data

onShow может сработать до окончательного создания дерева Android View. Короткие команды к ещё не смонтированной карте ставятся в очередь и выполняются после регистрации соответствующего элемента. При этом разметка процесса должна содержать сам картографический элемент: последующий Show() с пустой разметкой может заменить уже созданный экран.

Хранение собственных точек

Карта отвечает за отображение, а не за долговременное хранение данных решения. Сохраняйте точки обычными словарями в _data:

self._data["my_locations"] = [
    {
        "id": "client_1",
        "name": "Клиент",
        "address": "Москва, ...",
        "latitude": 55.75,
        "longitude": 37.62,
    }
]

Один массив можно показать в обычном Table и отрисовать через MapPoint. Адрес является необязательным полем: название и координаты можно сохранить без вызова геокодера. При выборе строки таблицы используйте координаты соответствующего элемента в MapCenter.

Открытие точки во внешнем навигаторе

from android import share_location, open_location

share_location(55.75, 37.62, "Клиент")
open_location(55.75, 37.62, "Клиент")

Используется системный geo:-intent Android, без привязки к конкретному приложению-навигации.

NodaScript

На Android доступны следующие команды общего картографического API:

map_call(map_id, action, params)
map_state(map_id)
map_camera(map_id, latitude, longitude, zoom)
map_center(map_id, latitude, longitude, optional_zoom)
map_point(map_id, object_id, latitude, longitude, text)
map_route(map_id, params)
map_search(map_id, query)
map_center_gps(map_id, zoom)
map_clear(map_id)
share_location(latitude, longitude, label)
open_location(latitude, longitude, label)

Для расширенных параметров используйте map_call:

map_call(
    'map',
    'add_polyline',
    {
        'id':'route_1',
        'points':[[55.75,37.61],[55.76,37.64]],
        'color':'#1565C0',
        'width':7
    }
);

Очистка, подбор масштаба и состояние

MapClear("map")                # Удалить обычные объекты
MapClear("map", routes=True)   # Также удалить построенные маршруты

MapFit(
    "map", [[55.75, 37.62], [55.77, 37.64]],
    max_zoom=14,
)

state = MapState("map")

Состояние содержит координаты и масштаб камеры, провайдера, число объектов и диагностические поля. Некоторые поля относятся только к отдельному SDK (например, источник API-ключа или состояние маршрутной сессии).

Провайдеры

YandexMap (Yandex MapKit)

YandexMap — элемент динамической разметки, использующий Yandex MapKit. Его методы отображения и взаимодействия вызываются через общий Map*-API; особенности поискового и маршрутного SDK описаны ниже.

Элемент YandexMap

Минимальная разметка:

{
  "type": "YandexMap",
  "id": "map",
  "height": 420,
  "center": [55.751225, 37.62954],
  "zoom": 13,
  "events": true
}

id обязателен. Для нескольких карт используйте разные id.

Параметры элемента:

  • center — начальные координаты [latitude, longitude];

  • zoom — начальный масштаб;

  • azimuth и tilt — направление и наклон камеры;

  • show_user_location — показывать местоположение устройства;

  • center_gps — при открытии центрировать карту по местоположению;

  • heading — направление устройства, если поддерживается SDK;

  • night — ночной режим;

  • style — пользовательский стиль MapKit;

  • route_color и route_width — цвет и толщина построенных маршрутов;

  • events — отправлять события NodaLogic, по умолчанию true;

  • objects — начальные объекты типов point, polyline, polygon, circle;

  • route_debug — диагностические Toast-сообщения маршрутизатора; в обычном решении оставляйте false.

Если высота не указана, применяется стандартная высота Android-компонента. Обычные параметры динамической разметки NodaLogic также применимы.

Ключ MapKit

Пользователь может задать ключ в настройках Android-клиента:

Настройки -> Maps -> Yandex MapKit API key.

Если в сборке предусмотрен платформенный ключ по умолчанию, пустое поле использует его. Пользовательский ключ имеет приоритет. MapKit получает ключ при первой инициализации процесса; после изменения ключа полностью перезапустите приложение.

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

Автоматическое построение маршрута

Текущая обвязка реализует автомобильные маршруты через MapKit:

MapRoute(
    "map",
    [
        [55.9292, 37.5207],
        [55.76594, 37.68549],
        [55.70306, 37.53028],
    ],
    routes=1, clear=True, fit=True,
    max_zoom=12, router_type="online",
)

Для старта с текущего положения устройства:

MapRoute(
    "map",
    [[55.9292, 37.5207], [55.70306, 37.53028]],
    from_gps=True, fit=True, router_type="online",
)

from_gps=True использует общий механизм GPS и при необходимости запрашивает разрешение Android.

Вызов асинхронный: немедленный ответ обычно содержит pending=true, затем приходит route_ready или route_error. Событие route_ready передаёт число маршрутов и число точек геометрии, но не весь массив координат. Если решение должно получить рассчитанную MapKit геометрию как массив, понадобится отдельное расширение платформенного API.

Важно

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

Поиск и обратное геокодирование

MapSearch("map", "Парк Горького", limit=10)
MapReverseGeocode("map", 55.75, 37.62)

Если область поиска не задана через MapCall, используется видимая область карты. Результаты приходят через событие search_ready. Элементы результата могут содержать name, description, address и points.

При обратном геокодировании поле query события равно reverse_geocode. Ошибки передаются в search_error. Результаты можно хранить как обычные данные решения; карта не сохраняет их автоматически.

Офлайновые карты Яндекса

MapKit может заранее скачать регионы и использовать их без соединения с интернетом. В Android-клиенте для управления такими загрузками предусмотрена вкладка Yandex экрана Офлайновые карты (если она включена в сборке). Список регионов и сами данные предоставляет MapKit: это не импорт произвольного файла карты с устройства.

Во вкладке можно просмотреть каталог регионов, начать загрузку, отслеживать прогресс, приостанавливать и возобновлять загрузку, обновлять и удалять скачанные данные. При загрузке используются возможности MapKit по управлению сетевым подключением и хранилищем офлайнового кеша. Загруженные регионы используются SDK автоматически; выбирать один «активный регион» для YandexMap не требуется.

Важно

Офлайновые регионы MapKit доступны только по платной лицензии Яндекса. Наличие вкладки в Android-клиенте само по себе не даёт доступа к скачиванию. Подготовьте нужные регионы при наличии сети и проверьте ключ и условия лицензии до работы устройства без подключения.

Для скачанных регионов MapKit документирует отображение карты, офлайновый поиск и построение автомобильных маршрутов без пробок. При этом не следует считать, что router_type="online" автоматически переключится на локальный расчёт: возможность и режим конкретного запроса нужно проверять отдельно. Аналогично не гарантируйте локальную работу MapReverseGeocode без проверки соответствующего сценария.

Официальная документация: Офлайновые карты MapKit для Android.

Практические рекомендации

  • Используйте общий Map*-API для прикладных действий, но проверяйте доступность провайдерских функций по таблице возможностей.

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

  • Для готового маршрута используйте MapPolyline; для самостоятельного расчёта маршрута SDK — MapRoute, если выбранный провайдер его поддерживает.

  • MapRoute, MapSearch и MapReverseGeocode выполняются асинхронно; обрабатывайте результат через события.

  • Не помещайте API-ключ MapKit в конфигурацию решения.

  • Если известны координаты, используйте MapCenter; MapCenterGPS предназначен для координат самого устройства.

  • В обычном интерфейсе отключайте route_debug, а большие SVG храните в константах обработчика.