Архитектура приложения

Архитектура приложений на Mapbox GL JS строится вокруг единого графического ядра, работающего поверх WebGL. Центральным объектом системы выступает экземпляр Map, который связывает стиль, источники данных, слои, пользовательские события и механизм отрисовки в единую реактивную модель.

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

Ключевые элементы архитектуры:

  • объект карты (Map) как контейнер состояния
  • стиль (Style) как декларация визуализации
  • источники данных (Source) как поставщики геометрии
  • слои (Layer) как правила отображения
  • события (Evented) как механизм взаимодействия

Объект карты как центральный координатор

Экземпляр Map выполняет роль диспетчера всех подсистем. Он:

  • инициализирует WebGL-контекст
  • загружает и применяет стиль
  • управляет жизненным циклом слоёв и источников
  • синхронизирует состояние с пользовательским вводом
  • координирует перерисовку через render loop

Внутри архитектуры важно понимать, что Map не является «контейнером данных». Он ближе к orchestrator-слою, который связывает независимые подсистемы через события и внутренние очереди задач.

Типичная схема инициализации:

const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/streets-v12',
  center: [69.2401, 41.2995],
  zoom: 10
});

При создании объекта запускается цепочка:

  1. инициализация WebGL renderer
  2. загрузка style JSON
  3. создание source cache
  4. подготовка layer tree
  5. запуск render loop

Стиль как декларативный слой архитектуры

Style в Mapbox GL JS представляет собой JSON-описание визуальной сцены. Он определяет не только внешний вид, но и структуру данных, которые будут загружены.

Основные компоненты стиля:

  • sources — источники геометрии
  • layers — правила визуализации
  • glyphs — шрифты для текста
  • sprites — иконки
  • transition — параметры анимации

Стиль можно рассматривать как промежуточный слой между данными и рендерингом. Он не содержит логики приложения, но определяет визуальную семантику.

Пример структуры:

{
  "version": 8,
  "sources": {},
  "layers": [],
  "glyphs": "mapbox://fonts/mapbox/{fontstack}/{range}.pbf",
  "sprite": "mapbox://sprites/mapbox/streets-v12"
}

Архитектурно важно, что стиль:

  • загружается асинхронно
  • может быть заменён динамически
  • триггерит полную перерасборку слоя сцены

Источники данных как слой абстракции геометрии

Sources отвечают за поставку данных в рендеринг-пайплайн. Они отделяют формат хранения от визуализации.

Основные типы:

  • vector — векторные тайлы
  • raster — растровые изображения
  • geojson — динамические данные
  • image — геопривязанные изображения
  • video — видеослои

Архитектурно источники работают как кэшируемые провайдеры данных. Они:

  • загружают тайлы по схеме z/x/y
  • кэшируют результаты
  • предоставляют данные в формате, оптимизированном для WebGL

Внутренний pipeline:

  1. запрос тайла
  2. проверка кэша
  3. загрузка сети
  4. декодирование
  5. передача в bucket хранения

Слои как декларативные правила рендеринга

Layers описывают, как данные из sources должны отображаться. Каждый слой:

  • привязан к source
  • имеет тип (fill, line, circle, symbol, raster, heatmap)
  • содержит фильтры
  • определяет стилистику

Важно, что слой не содержит данных. Он только интерпретирует их.

Архитектурная модель слоёв — это стек, где порядок определяет приоритет отрисовки.

map.addLayer({
  id: 'roads',
  type: 'line',
  source: 'streets',
  paint: {
    'line-color': '#ff0000',
    'line-width': 2
  }
});

Слои компилируются в WebGL-шейдеры, что делает их частью GPU pipeline.

Рендеринг как непрерывный цикл

Render loop — ключевой элемент архитектуры. Он работает по принципу «перерисовывать только при изменениях», но фактически поддерживает постоянный цикл при наличии анимаций или взаимодействий.

Основные триггеры перерисовки:

  • изменение камеры (pan/zoom/rotate)
  • обновление источников
  • изменение стиля
  • пользовательские события
  • анимации transition

Пайплайн рендеринга:

  1. расчет камеры (camera state)
  2. определение видимых тайлов
  3. сбор данных из sources
  4. применение layer filters
  5. генерация WebGL draw calls
  6. отрисовка кадра

WebGL-слой и GPU-ориентированная архитектура

Внутри используется WebGL как низкоуровневая графическая подсистема. Это определяет архитектурные ограничения:

  • данные должны быть буферизованы
  • вычисления смещены в шейдеры
  • минимизация CPU операций критична

Mapbox GL JS компилирует style layers в набор:

  • vertex shaders
  • fragment shaders
  • attribute buffers

Таким образом, каждый слой становится GPU-программой.

Событийная модель и взаимодействие

Архитектура взаимодействия основана на event-driven подходе. Map и связанные объекты наследуют поведение Evented.

Основные категории событий:

  • загрузка (load, idle)
  • камера (move, zoom, rotate)
  • данные (data, sourcedata)
  • ошибки (error)

События обеспечивают слабую связанность между слоями приложения и картой.

Пример:

map.on('move', () => {
  console.log(map.getCenter());
});

С точки зрения архитектуры это заменяет необходимость polling и прямого контроля состояния.

Управление состоянием карты

Состояние карты разделяется на несколько уровней:

  1. UI state

    • положение камеры
    • масштаб
    • угол поворота
  2. Data state

    • загруженные источники
    • кэш тайлов
  3. Style state

    • активные слои
    • фильтры
    • визуальные параметры

Изменение состояния происходит через императивные API, но результат всегда приводит к декларативной перерисовке.

Модульная структура приложения

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

  • модуль карты (инициализация Map)
  • модуль данных (API, загрузка GeoJSON)
  • модуль слоёв (добавление/удаление layers)
  • модуль взаимодействия (events, controls)
  • модуль UI (панели, фильтры)

Такое разделение важно, поскольку сам Mapbox GL JS не навязывает архитектуру приложения, а предоставляет только графическое ядро.

Интеграция с современными фронтенд-архитектурами

В SPA-приложениях карта становится частью реактивного дерева компонентов.

Особенности интеграции:

  • карта живёт вне виртуального DOM
  • состояние камеры синхронизируется с store (Redux, Pinia, Zustand)
  • источники обновляются через эффекты

Типичная проблема архитектуры — контроль жизненного цикла Map:

  • создание при mount
  • уничтожение при unmount
  • предотвращение повторной инициализации

Оптимизация и архитектурные ограничения

Ключевые ограничения, влияющие на архитектуру:

  • WebGL context limits
  • стоимость пересборки стиля
  • размер векторных тайлов
  • частота перерисовки

Стратегии оптимизации:

  • минимизация числа слоёв
  • использование фильтрации вместо дублирования sources
  • кэширование GeoJSON
  • дебаунс обновлений камеры
  • lazy loading источников

Пайплайн данных внутри системы

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

Sources → Tile Cache → Feature Processing → Layer Evaluation → GPU Buffers → Render

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

Роль абстракции камеры

Camera в архитектуре выступает как независимый математический слой. Она определяет:

  • матрицу трансформации
  • проекцию координат
  • видимую область

Любое взаимодействие пользователя преобразуется в изменения camera state, а не напрямую в графику.

Масштабируемость архитектуры

Система масштабируется за счёт:

  • tile-based данных
  • GPU-ускорения
  • разделения style/sources/layers
  • ленивой загрузки ресурсов

Это позволяет отображать огромные наборы геоданных без изменения архитектуры приложения на уровне кода разработчика.