PerfectVector
작성: Irene Kim

MapLibre SVG 아이콘: 픽셀과 심볼 크기 확인하기

MapLibre 심볼 레이어용 SVG 그림을 디코딩하고 이미지 밀도와 아이콘 크기를 조절하세요. 편집 가능한 원본을 보관하고 표시되는 모양과 구멍을 확인합니다.

7분 읽기
목차

MapLibre GL JS에서 SVG 아이콘 이미지를 사용하려면 브라우저의 Image로 SVG를 디코딩하고, 그 이미지를 addImage에 전달한 뒤 심볼 레이어의 icon-image에서 해당 ID를 참조하세요. 원본 SVG는 별도로 보관합니다. 등록된 스타일 이미지는 편집 가능한 SVG 경로 객체가 아니라 렌더링용 픽셀을 제공합니다.

현재 공식 SVG 심볼 예제가 이 디코딩 방식을 보여 줍니다. MapLibre가 SVG 그림을 표시할 수 없다고 단정하면 이 방법을 놓치게 됩니다. 이 예제는 6.12.0으로 버전을 고정하고 토큰이 필요 없는 빈 지도에서 직접 만든 속 빈 엠블럼을 시험합니다.

원본과 등록된 이미지 분리하기

보관하는 원본에는 안쪽 구멍이 있는 복합 마름모 경로 하나가 있습니다. viewBox는 0 0 64 64로 유지됩니다. 너비와 높이를 64 또는 128로 명시한 사본 두 개를 만들되 경로 데이터는 동일하게 유지합니다.

브라우저 디코딩은 각 사본을 명시된 치수의 이미지로 바꿉니다. MapLibre의 addImage API는 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로 등록하면 icon-size가 1일 때 64×64 이미지를 비율 1로 등록한 것과 같은 64×64 명목상 프레임이 됩니다. 첫 번째 비율을 1로 바꾸면 프레임이 두 배가 됩니다. icon-size를 1.5로 키우면 64×64 논리 이미지 프레임이 96×96이 됩니다.

이 프레임에는 원본 여백도 포함됩니다. 프레임 안의 엠블럼이 너무 작아 보인다면 모든 심볼 크기를 키우기 전에 SVG 경계를 확인하세요. 같은 비율에서 디코딩 이미지가 커지면 심볼도 커집니다. 입력 치수만으로 밀도 전략이 되는 것은 아닙니다.

지도 캔버스에는 자체 픽셀 비율이 있습니다. 이는 addImage에 전달하는 비율과 별개입니다. 측정용 시험에서는 문서화된 Map 옵션으로 캔버스 비율을 1로 명시했고, 브라우저의 기기 픽셀 비율은 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

모든 가장자리 픽셀이 그 색상 임곗값을 통과하지 않으므로 측정한 채색 경계가 명목상 프레임보다 작습니다. 각 쌍은 이 시험에서 의도한 크기 관계를 보여 주지만, 모든 밀도나 확대율에서 픽셀 품질이 동일함을 입증하지는 않습니다.

등록 이미지의 가운데 알파는 모든 경우에 0이었습니다. 렌더링한 지도에서는 배경 레이어 자체가 불투명하므로 그 위치에 배경색 (244, 242, 232)와 알파 255가 나타났습니다. 구멍을 통해 불투명한 지도 픽셀이 보인다고 아이콘이 투명도를 잃은 것은 아닙니다.

이는 이 원본, 브라우저, 캔버스 설정, 임곗값 안에서의 관찰입니다. 특히 가는 스트로크나 작은 구멍이 있다면 전달할 그림을 의도한 표시 크기에서 시험하세요.

토큰 없는 심볼 시작 예제 복사하기

다음의 완전한 HTML을 저장하고 로컬 웹 서버로 제공하세요. 고정 버전 라이브러리와 CSS를 가져오려면 인터넷이 필요하지만 지도는 타일 서비스나 API 키를 사용하지 않습니다. SVG는 텍스트로 내장한 뒤 데이터 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 프레임이 됩니다. 저밀도 입력과 비교하려면 경로와 viewBox는 같은 상태로 두고 SVG 너비와 높이를 64, ratio를 1로 바꾸세요.

icon-image는 등록한 ID와 같게 유지하세요. 아이콘이 보이지 않으면 디코딩과 등록이 성공했는지 확인한 뒤 소스, 레이어, 표시 여부, 배치 설정을 살펴봅니다. 이 단일 점 예제는 다른 심볼과의 충돌과 무관하게 비교하도록 icon-overlap: 'always'를 사용합니다. 실제 서비스의 스타일에는 다른 충돌 동작이 필요할 수 있습니다.

전달 방식에 맞다면 DOM 마커 사용하기

MapLibre는 DOM Marker를 사용한 사용자 지정 아이콘도 문서화합니다. 이 방식은 스타일 이미지를 등록하는 대신 HTML 요소를 감쌉니다. 요소 기반 마커 작업이 애플리케이션에 맞을 때 선택하고 브라우저 스타일과 배치를 별도로 확인하세요.

Mapbox Studio의 벡터 아이콘 기능이나 MapTiler SDK 예제의 주장을 이 MapLibre 스타일 이미지 파이프라인에 그대로 적용하지 마세요. 일반 풀컬러 SVG 이미지는 sdf 플래그만 바꾼다고 부호 있는 거리장이 되지도 않습니다. 밀도 설정은 이미지의 해석과 표시 방식을 제어하며, 픽셀을 편집 가능한 윤곽으로 바꾸지는 않습니다.

대상 환경에 PNG 파일이 필요하다면 Sharp SVG 렌더링 가이드에서 전달용 래스터 사본을 준비하는 방법을 확인하세요. 일반 SVG 원본은 편집용으로 보관합니다. 인라인 SVG와 이미지 전달은 상호작용 방식도 다릅니다.

아이콘이 PNG나 JPG로만 있고 편집 가능한 그림이 필요하다면 PerfectVector로 SVG 윤곽을 복원하고 바깥 모양과 구멍을 확인한 뒤, 지도 이미지를 디코딩하기 전에 그 원본을 보관하세요. 밀도, 심볼 크기, 지리적 위치는 여전히 지도 코드가 제어합니다. 기존의 깔끔한 SVG는 바로 디코딩 과정으로 보낼 수 있습니다. 벡터화 가이드는 래스터 변환이 언제 유용한지 설명합니다.

자주 묻는 질문

MapLibre GL JS에서 SVG 그림을 심볼로 표시할 수 있나요? 네. 현재 공식 예제는 브라우저 Image로 SVG 텍스트를 디코딩하고 그 이미지를 심볼 레이어에 등록합니다. 원본 SVG는 등록 이미지와 별도로 보관하세요.

더 큰 입력을 쓰자 아이콘이 커진 이유는 무엇인가요? 같은 이미지 픽셀 비율과 icon-size를 유지하면서 디코딩 치수를 키우면 명목상 프레임이 커집니다. 이미지 밀도와 표시 배율을 의도적으로 함께 설정하세요.

pixelRatio는 심볼에서 편집 가능한 SVG 경로를 보존하나요? 아닙니다. 이미지 밀도를 나타냅니다. 원본 SVG를 보관하고 편집한 다음 수정 그림을 지도용으로 디코딩하세요.

아이콘 구멍 안의 지도 픽셀이 불투명한 이유는 무엇인가요? 불투명한 배경 레이어가 투명한 아이콘 구멍을 통해 보일 수 있습니다. 등록 이미지의 알파와 최종 합성된 지도 캔버스를 따로 확인하세요.

출처

  1. MapLibre — 원격 SVG 심볼 표시 — 현재의 SVG 텍스트, 브라우저 이미지, addImage 방식입니다.
  2. MapLibre — Map API — 허용하는 이미지 객체와 런타임 이미지 등록입니다.
  3. MapLibre — StyleImageMetadata — 이미지 밀도와 SDF 해석 메타데이터입니다.
  4. MapLibre — 심볼 레이아웃 규격 — 아이콘 배율과 충돌 동작입니다.
  5. MapLibre — MapOptions — 측정용 시험 조건을 제어한 캔버스 옵션입니다.
  6. MDN — HTMLImageElement.decode — 브라우저 이미지 디코딩을 기다리고 실패를 처리하는 방법입니다.
  7. MapLibre — Marker로 사용자 지정 아이콘 추가 — 별도의 HTML 요소 기반 마커 과정입니다.

블로그의 다른 글

PerfectVector

편집하기 쉬운
더 깔끔한 SVG로 다시 시작하세요

신용카드 불필요