Описания для изображений

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

Текстовое описание изображения не ограничивается HTML-атрибутом alt. В Mapbox GL JS оно также проявляется в виде идентификаторов иконок, метаданных слоёв, подписей в всплывающих окнах и структурированных свойств GeoJSON.


Alt-текст и доступность в веб-картах

При работе с изображениями, которые отображаются вне контекста canvas карты (например, в пользовательских компонентах интерфейса), применяется стандартный механизм HTML-доступности:

<img src="marker.png" alt="Ресторан на углу улицы с террасой">

В картах же основная визуализация происходит внутри WebGL, поэтому прямой alt отсутствует. Вместо него используется концепция семантических заменителей:

  • свойства GeoJSON (properties.title, properties.description)
  • всплывающие окна (Popup)
  • атрибуты символов (text-field, icon-text-fit)
  • описательные ключи в стилях

Пример использования описания через свойства данных:

{
  type: "Feature",
  geometry: {
    type: "Point",
    coordinates: [69.5901, 42.3417]
  },
  properties: {
    name: "Городской парк",
    description: "Зелёная зона с пешеходными дорожками и фонтаном"
  }
}

Эти поля затем используются для генерации описаний в интерфейсе, заменяя необходимость прямого alt-текста.


Описания маркеров и символов

В Mapbox GL JS маркеры могут быть реализованы двумя способами:

  • DOM-маркеры (new mapboxgl.Marker())
  • символические слои (symbol layer)

DOM-маркеры позволяют использовать HTML-элементы, где описание изображения задаётся напрямую:

const el = document.createElement('div');
el.className = 'marker';

const img = document.createElement('img');
img.src = 'restaurant.png';
img.alt = 'Маркер ресторана';

el.appendChild(img);

new mapboxgl.Marker(el)
  .setLngLat([69.5901, 42.3417])
  .addTo(map);

В случае символических слоёв описание изображения переносится в метаданные и идентификаторы:

map.addLayer({
  id: 'restaurants',
  type: 'symbol',
  source: 'places',
  layout: {
    'icon-image': 'restaurant-15',
    'text-field': ['get', 'name'],
    'text-offset': [0, 1.2]
  }
});

Здесь визуальная иконка (icon-image) не имеет текстового описания напрямую, но её семантика задаётся через:

  • имя слоя
  • название источника данных
  • текстовые подписи

Работа с icon-image и sprite

Иконки в Mapbox GL JS обычно хранятся в sprite-атласе. Каждое изображение имеет идентификатор, который фактически выступает как его описание в системе.

map.loadImage('/icons/cafe.png', (error, image) => {
  if (error) throw error;

  map.addImage('cafe-icon', image);

  map.addLayer({
    id: 'cafes',
    type: 'symbol',
    source: 'cafes-data',
    layout: {
      'icon-image': 'cafe-icon'
    }
  });
});

В этом контексте cafe-icon выполняет роль семантического описания изображения. Хотя это не текст для пользователя, оно важно для:

  • читаемости кода
  • переиспользования ресурсов
  • логической структуры стиля

При использовании sprite-атласа описания изображений становятся частью JSON-структуры:

{
  "cafe": {
    "x": 0,
    "y": 0,
    "width": 32,
    "height": 32,
    "pixelRatio": 2
  }
}

Каждый ключ в sprite можно рассматривать как именованное описание визуального элемента.


Добавление изображений через addImage

Метод addImage позволяет регистрировать изображения в стиле карты. Здесь важную роль играет имя изображения, которое фактически является его описанием в системе.

map.addImage('hospital-icon', image, {
  sdf: true
});

Имя 'hospital-icon' выполняет несколько функций:

  • идентифицирует изображение в стиле
  • используется в слоях через icon-image
  • служит семантическим маркером назначения

При большом количестве иконок становится критичным соблюдение единообразной системы именования:

  • poi-restaurant
  • poi-hotel
  • transport-bus
  • health-hospital

Такая структура заменяет необходимость текстовых описаний на уровне UI.


Изображения в всплывающих окнах

Popup-компоненты часто содержат изображения, где текстовое описание становится обязательным элементом разметки:

new mapboxgl.Popup()
  .setLngLat([69.5901, 42.3417])
  .setHTML(`
    <div>
      <img src="park.jpg" alt="Парк с фонтаном и аллеями">
      <h3>Городской парк</h3>
      <p>Зона отдыха в центре города</p>
    </div>
  `)
  .addTo(map);

Здесь описание изображения влияет на:

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

В более сложных случаях описание формируется динамически:

function createPopup(feature) {
  return new mapboxgl.Popup().setHTML(`
    <img src="${feature.properties.image}"
         alt="${feature.properties.description}">
    <p>${feature.properties.name}</p>
  `);
}

Генерация описаний из GeoJSON-данных

Текстовые описания изображений часто извлекаются из структуры данных GeoJSON. Это позволяет централизовать управление семантикой:

map.on('click', 'places', (e) => {
  const feature = e.features[0];

  new mapboxgl.Popup()
    .setLngLat(feature.geometry.coordinates)
    .setHTML(`
      <img src="${feature.properties.image}"
           alt="${feature.properties.alt}">
      <strong>${feature.properties.name}</strong>
      <p>${feature.properties.description}</p>
    `)
    .addTo(map);
});

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


Семантика изображений в слоях и стилях

В style specification Mapbox изображения часто не существуют как DOM-элементы. Их описание распределяется между несколькими уровнями:

  • идентификатор изображения (icon-image)
  • описание слоя (layer.id)
  • подписи (text-field)
  • фильтры (filter)

Пример семантически насыщенного слоя:

map.addLayer({
  id: 'tourism-points',
  type: 'symbol',
  source: 'tourism',
  layout: {
    'icon-image': ['get', 'icon'],
    'text-field': ['get', 'title'],
    'text-size': 12
  }
});

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


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

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

  • использование шаблонных строк в text-field
  • централизованное хранение описаний в properties
  • минимизация HTML в popup

Пример шаблона:

'text-field': [
  'format',
  ['get', 'name'], {},
  '\n',
  ['get', 'category'], { 'font-scale': 0.8 }
]

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


Ошибки при работе с описаниями изображений

Часто встречаются следующие проблемы:

  • отсутствие alt у DOM-маркеров
  • использование неинформативных идентификаторов (icon1, img2)
  • дублирование описаний в GeoJSON и popup
  • несогласованность текстов между слоями и всплывающими окнами
  • хранение описаний в UI-коде вместо данных

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