Загрузка пользовательских стилей

В Mapbox GL JS визуальное оформление карты полностью определяется объектом стиля. Стиль задаёт источники данных, слои визуализации, шрифты, спрайты и базовые параметры рендеринга. Загрузка пользовательского стиля — ключевой этап построения интерактивных карт, поскольку именно на этом уровне формируется внешний вид и поведение всей сцены WebGL.

Форматы подключения стиля

В Mapbox GL JS поддерживаются два основных способа задания стиля: через URL и через объект JSON.

Стиль по URL

Наиболее распространённый вариант — использование хостинга Mapbox:

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

URL указывает на заранее опубликованный стиль в Mapbox Studio. Такой подход обеспечивает автоматическое обновление ресурсов (слоёв, тайлов, спрайтов и шрифтов) без необходимости ручного управления зависимостями.

Стиль как объект

Альтернативный вариант — передача полного JSON-объекта стиля:

const customStyle = {
  version: 8,
  sources: {
    cities: {
      type: 'geojson',
      data: '/data/cities.geojson'
    }
  },
  layers: [
    {
      id: 'cities-layer',
      type: 'circle',
      source: 'cities',
      paint: {
        'circle-radius': 6,
        'circle-color': '#ff5200'
      }
    }
  ]
};

const map = new mapboxgl.Map({
  container: 'map',
  style: customStyle
});

Такой подход используется при динамическом формировании стиля или работе с локальными данными без подключения к Mapbox Studio.

Структура пользовательского стиля

Любой стиль Mapbox GL JS базируется на спецификации Style Specification v8.

Ключевые компоненты:

  • version — версия спецификации (обычно 8)
  • sources — источники данных (vector, raster, geojson, image, video)
  • layers — слои рендеринга
  • glyphs — URL для шрифтов
  • sprite — набор иконок
  • transition — параметры анимации изменений

Пример расширенной структуры:

{
  version: 8,
  sprite: 'mapbox://sprites/mapbox/streets-v12',
  glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
  sources: { ... },
  layers: [ ... ],
  transition: {
    duration: 300,
    delay: 0
  }
}

Загрузка и жизненный цикл стиля

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

Основные события:

  • load — карта и стиль полностью загружены
  • style.load — стиль загружен и применён
  • styledata — изменения в структуре стиля
  • render — обновление кадра

Пример корректного ожидания загрузки:

map.on('load', () => {
  map.addSource('points', {
    type: 'geojson',
    data: '/data/points.geojson'
  });

  map.addLayer({
    id: 'points-layer',
    type: 'circle',
    source: 'points'
  });
});

Работа со слоями до события load приводит к ошибкам, так как WebGL-контекст инициализируется только после применения стиля.

Динамическая смена стиля

Mapbox GL JS позволяет менять стиль на лету:

map.setStyle('mapbox://styles/mapbox/dark-v11');

При вызове setStyle происходит полная перезагрузка графа рендеринга: источники, слои и ресурсы пересоздаются.

Для восстановления пользовательских слоёв используется обработка события повторной загрузки:

map.on('style.load', () => {
  map.addSource('custom-data', {
    type: 'geojson',
    data: '/data/custom.geojson'
  });

  map.addLayer({
    id: 'custom-layer',
    type: 'fill',
    source: 'custom-data',
    paint: {
      'fill-color': '#0080ff',
      'fill-opacity': 0.5
    }
  });
});

Сохранение пользовательских слоёв при смене стиля

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

const customLayers = [
  {
    id: 'custom-layer',
    type: 'circle',
    source: 'custom-source',
    paint: {
      'circle-radius': 5,
      'circle-color': '#00ff00'
    }
  }
];

map.setStyle('mapbox://styles/mapbox/light-v11');

map.once('style.load', () => {
  map.addSource('custom-source', {
    type: 'geojson',
    data: '/data/custom.geojson'
  });

  customLayers.forEach(layer => map.addLayer(layer));
});

Использование локальных стилей

При работе без Mapbox Studio стиль может храниться как локальный JSON-файл:

fetch('/styles/local-style.json')
  .then(res => res.json())
  .then(style => {
    map.setStyle(style);
  });

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

Проверка состояния загрузки стиля

Mapbox GL JS предоставляет методы для контроля состояния:

  • map.isStyleLoaded() — проверка завершения загрузки
  • map.getStyle() — получение текущего стиля
if (map.isStyleLoaded()) {
  console.log(map.getStyle());
}

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

Обновление стиля без полной перезагрузки

Для частичных изменений используется работа с API стиля:

  • setPaintProperty
  • setLayoutProperty
  • setFilter
map.setPaintProperty('points-layer', 'circle-color', '#ff0000');

Этот подход значительно эффективнее, чем повторный setStyle, поскольку не разрушает текущий граф сцены.

Работа с кастомными источниками в стилях

Источники данных определяются внутри стиля и могут подключаться динамически:

map.addSource('roads', {
  type: 'vector',
  url: 'mapbox://mapbox.mapbox-streets-v8'
});

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

Особенности кэширования стилей

Браузерное кэширование играет важную роль при работе со стилями, особенно если используется CDN Mapbox. Изменения в стиле могут не отображаться мгновенно из-за кэширования:

  • sprite.json
  • glyphs.pbf
  • tiles

Для обхода используются версии стилей или уникальные URL-параметры.

Конфликты и приоритеты слоёв

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

layers: [
  { id: 'background', type: 'background' },
  { id: 'roads', type: 'line' },
  { id: 'labels', type: 'symbol' }
]

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

Оптимизация пользовательских стилей

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

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

Избыточная детализация стиля напрямую влияет на FPS и время рендеринга.

Версионирование стилей

Mapbox GL JS строго привязан к версии спецификации. Использование неподдерживаемых полей приводит к игнорированию части конфигурации или ошибкам рендеринга.

Рекомендуемая практика — фиксировать version: 8 и проверять совместимость при миграции между версиями библиотек и стилевых схем.

Асинхронная модификация стиля

После загрузки стиля изменения должны выполняться с учётом асинхронности:

map.on('styledata', () => {
  // безопасное изменение структуры стиля
});

Событие styledata срабатывает при любом изменении источников или слоёв, включая внутренние операции Mapbox GL JS.

Поведение при ошибках загрузки

При недоступности ресурсов стиль может загружаться частично. В этом случае:

  • слои без источников не рендерятся
  • отсутствующие glyphs заменяются fallback-шрифтом
  • sprite-иконки пропускаются

Корректная обработка ошибок предполагает мониторинг событий error:

map.on('error', (e) => {
  console.log(e.error);
});

Интеграция пользовательских стилей с внешними данными

Часто стиль используется как слой абстракции над внешними источниками данных. В этом случае структура строится вокруг динамических URL и параметризованных источников:

sources: {
  dynamicData: {
    type: 'geojson',
    data: () => fetch('/api/data').then(r => r.json())
  }
}

Такой подход позволяет строить адаптивные карты, изменяющиеся в зависимости от состояния приложения или пользовательского запроса.