PerfectVector
Автор: Irene Kim

Значки SVG MapLibre: пиксели и размер символа

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

8 мин чтения
На этой странице

Чтобы использовать изображение значка SVG в MapLibre GL JS, декодируйте SVG браузерным Image, передайте изображение в addImage и укажите его ID в icon-image слоя символов. Храните исходный SVG отдельно: зарегистрированное изображение стиля предоставляет пиксели для отрисовки, а не редактируемые объекты контуров SVG.

Текущий официальный пример символа SVG демонстрирует этот способ декодирования. Общее утверждение, что MapLibre не может показывать SVG-графику, упустило бы его. Наш пример закрепляет 6.12.0 и проверяет оригинальную полую эмблему на пустой карте без токена.

Держите исходник отдельно от зарегистрированного изображения

Сохраняемый исходник содержит один составной ромбический контур с внутренним отверстием. Его viewBox остаётся 0 0 64 64. Мы создаём две копии с явными шириной и высотой 64 или 128; данные контура остаются одинаковыми.

Браузерное декодирование превращает каждую копию в изображение с объявленными размерами. API addImage MapLibre принимает объекты изображений, такие как HTMLImageElement и ImageData. Он не принимает контур SVG как инструкцию геометрии символа.

Это различие важно при смене цвета или очертаний. Отредактируйте мастер SVG, декодируйте изменённое изображение и обновите изображение карты через соответствующий API. Проверка зарегистрированных пикселей не восстановит исходное построение контура. Наше руководство по SVG со встроенным растром объясняет более широкое различие контейнера SVG и редактируемой геометрии.

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

Выбирайте плотность изображения и размер значка вместе

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

Для нашего центрированного символа без поворота и подгонки под текст номинальная рамка такова:

logical width = decoded width / image pixelRatio * icon-size
logical height = decoded height / image pixelRatio * icon-size

Изображение 128×128, зарегистрированное с отношением 2, даёт ту же номинальную рамку 64×64, что изображение 64×64 с отношением 1, когда icon-size равен 1. Изменение первого отношения на 1 удваивает рамку. Увеличение icon-size до 1,5 превращает логическую рамку изображения 64×64 в 96×96.

Эта рамка включает пустое пространство исходника. Если эмблема внутри выглядит слишком маленькой, проверьте границы SVG, прежде чем увеличивать все символы. Большее декодированное изображение при том же отношении также увеличивает символ; одни входные размеры не являются стратегией плотности.

У холста карты есть собственное пиксельное отношение. Оно отдельно от отношения, передаваемого в addImage. Наш измерительный пример явно задаёт отношение холста 1 через документированные параметры Map; отношение пикселей устройства в браузере было 2. Это изолирует следующие сравнения, не принимая аппаратные пиксели снимка за размеры CSS.

Сравните фактический закрашенный след

Мы отрисовали пять вариантов символов в Chrome с WebGL 2. На каждой карте была одна точка, одинаковый светлый фон, не было тайлов и токена. Оба исходника SVG содержали один одинаковый контур без встроенного изображения.

Чтобы измерить терракотовую графику, наше считывание сканировало реальные пиксели WebGL на преобладание красного: красный больше зелёного в 1,35 раза и синего в 1,6 раза. Размеры найденного ограничивающего прямоугольника мы делили на отношение внутреннего буфера холста к CSS. Это измеряет закрашенную область по порогу, а не полную рамку изображения.

Декодированное изображениеОтношение изображенияicon-sizeНоминальная рамкаНаблюдаемые закрашенные границы
64×641164×6462×62 CSS px
128×12811128×128126×126 CSS px
128×1282164×6462×62 CSS px
64×6411.596×9694×94 CSS px
128×12821.596×9694×94 CSS px

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

Альфа-канал центра зарегистрированного изображения был нулевым во всех случаях. На отрисованной карте это место показывало цвет фона (244, 242, 232) с альфа-каналом 255, поскольку сам фоновый слой непрозрачен. Непрозрачные пиксели карты, видимые через отверстие, не означают, что значок потерял прозрачность.

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

Скопируйте стартовый пример символа без токена

Сохраните этот полный HTML и раздавайте локальным веб-сервером. Для закреплённой библиотеки и CSS нужен интернет; карта не использует сервис тайлов или API-ключ. SVG встроен как текст и закодирован в data URL.

Пример ожидает image.decode(), поэтому регистрация следует после успешного декодирования. Он регистрирует известное изображение после загрузки стиля; для этой единственной фиксированной эмблемы не нужен обработчик отсутствующих изображений из официального примера.

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>MapLibre decoded SVG symbol</title>
<link rel="stylesheet"
      href="https://unpkg.com/maplibre-gl@6.12.0/dist/maplibre-gl.css">
<style>#map { width: 360px; height: 280px; }</style>
<div id="map"></div>
<pre id="status">Loading…</pre>
<script type="module">
import * as ml from
  'https://unpkg.com/maplibre-gl@6.12.0/dist/maplibre-gl.mjs';
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 64 64"><path fill="#c45b39" fill-rule="evenodd" d="M32 0 L64 32 L32 64 L0 32 Z M32 16 L16 32 L32 48 L48 32 Z"/></svg>`;
const image = new Image();
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
try {
  await image.decode();
  const ratio = 2;
  const iconSize = 1;
  const map = new ml.Map({
    container: 'map', center: [0, 0], zoom: 2, interactive: false,
    attributionControl: false, fadeDuration: 0,
    style: { version: 8, sources: {}, layers: [{
      id: 'background', type: 'background',
      paint: { 'background-color': '#f4f2e8' }
    }] }
  });
  map.on('error', e => {
    document.querySelector('#status').textContent = e.error.message;
  });
  await map.once('load');
  map.addImage('emblem', image, { pixelRatio: ratio });
  map.addSource('point', { type: 'geojson', data: {
    type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] },
    properties: {}
  } });
  map.addLayer({ id: 'emblem', type: 'symbol', source: 'point', layout: {
    'icon-image': 'emblem', 'icon-size': iconSize, 'icon-overlap': 'always'
  } });
  await map.once('idle');
  const registered = map.getImage('emblem');
  document.querySelector('#status').textContent = JSON.stringify({
    version: ml.getVersion(),
    decoded: [image.naturalWidth, image.naturalHeight],
    registered: [registered.data.width, registered.data.height],
    imagePixelRatio: registered.pixelRatio,
    logicalFrame: [image.naturalWidth / ratio * iconSize,
                   image.naturalHeight / ratio * iconSize]
  }, null, 2);
} catch (error) {
  document.querySelector('#status').textContent = String(error);
}
</script>
</html>

Состояние должно показать декодированные и зарегистрированные размеры [128, 128], отношение изображения 2 и логическую рамку [64, 64]. Оно сообщает номинальную рамку, а не измеренный прямоугольник закрашенной области. Измените iconSize на 1,5 для рамки 96×96. Для сравнения входа низкой плотности измените ширину и высоту SVG на 64, а ratio на 1, сохранив те же контур и viewBox.

Держите icon-image равным зарегистрированному ID. Если значок не появляется, подтвердите успешное декодирование и регистрацию, затем проверьте исходник, слой, видимость и настройки размещения. Наш пример с одной точкой использует icon-overlap: 'always', чтобы сравнение не зависело от столкновений с другими символами. Рабочему стилю может понадобиться иное поведение столкновений.

Используйте DOM-маркер, если такова модель передачи

MapLibre также описывает собственные значки с DOM Markers. Этот способ оборачивает HTML-элемент вместо регистрации изображения стиля. Выбирайте его, если процесс маркеров на основе элементов подходит приложению, затем отдельно проверяйте браузерное оформление и размещение.

Не переносите утверждения о векторных значках Mapbox Studio или примере MapTiler SDK на этот конвейер изображений стиля MapLibre. Обычное полноцветное изображение SVG также не становится знаковым полем расстояний от одного изменения флага sdf. Настройки плотности управляют интерпретацией и показом изображения; они не превращают пиксели в редактируемые контуры.

Если целевой среде нужен файл PNG, наше руководство по отрисовке SVG в Sharp описывает подготовку растровой копии для передачи. Сохраняйте обычный мастер SVG для правок; встроенный SVG и передача изображения также имеют разные модели взаимодействия.

Если значок существует только в PNG или JPG, а нужна редактируемая графика, восстановите контуры SVG с PerfectVector, проверьте внешнюю форму и отверстие и сохраните мастер до декодирования изображения для карты. Код карты по-прежнему управляет плотностью, размером символа и географической позицией. Чистый существующий SVG может сразу пройти процесс декодирования; руководство по векторизации объясняет, когда полезно преобразование растра.

Частые вопросы

Может ли MapLibre GL JS показывать SVG-графику как символ? Да. Текущий официальный пример декодирует SVG-текст браузерным Image и регистрирует это изображение для слоя символов. Храните оригинальный SVG отдельно от зарегистрированного изображения.

Почему больший вход увеличил значок? Увеличение декодированных размеров при том же пиксельном отношении изображения и icon-size увеличивает номинальную рамку. Осознанно сочетайте плотность изображения и масштаб отображения.

Сохраняет ли pixelRatio редактируемые контуры SVG в символе? Нет. Он описывает плотность изображения. Сохраняйте и редактируйте исходный SVG, затем декодируйте обновлённую графику для карты.

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

Источники

  1. MapLibre — Показ удалённого символа SVG — Текущий путь SVG-текста, браузерного изображения и addImage.
  2. MapLibre — API Map — Принимаемые объекты изображений и регистрация изображений во время выполнения.
  3. MapLibre — StyleImageMetadata — Метаданные плотности изображения и интерпретации SDF.
  4. MapLibre — Спецификация компоновки символов — Масштабирование значка и поведение столкновений.
  5. MapLibre — MapOptions — Параметры холста для управления измерительным примером.
  6. MDN — HTMLImageElement.decode — Ожидание браузерного декодирования изображения и обработка ошибок.
  7. MapLibre — Добавление собственных значков с Markers — Отдельный процесс маркеров на основе HTML-элементов.

Ещё из блога

PerfectVector

Начните с более чистого SVG,
который проще редактировать

Без банковской карты