PerfectVector
โดย Irene Kim

ไอคอน SVG ใน MapLibre: ตรวจพิกเซลและขนาดสัญลักษณ์

ถอดรหัส SVG สำหรับเลเยอร์สัญลักษณ์ MapLibre ควบคุมความหนาแน่นภาพกับขนาดไอคอน และเก็บต้นฉบับแก้ไขได้ ตรวจรูปกับช่องที่แสดง

อ่าน 4 นาที
ในหน้านี้

หากต้องการใช้ภาพไอคอน 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 ก่อนเพิ่มขนาดทุกสัญลักษณ์ ภาพถอดรหัสใหญ่ขึ้นที่อัตราส่วนเดิมทำให้สัญลักษณ์ใหญ่ขึ้นด้วย ขนาดอินพุตอย่างเดียวไม่ใช่กลยุทธ์ความหนาแน่น

Canvas แผนที่มีอัตราส่วนพิกเซลของตน แยกจากอัตราส่วนที่ส่งให้ addImage ไฟล์วัดของเราตั้งอัตราส่วน Canvas เป็น 1 ชัดเจนผ่านตัวเลือก Map ตามเอกสาร ส่วน device pixel ratio ของเบราว์เซอร์คือ 2 วิธีนี้แยกการเทียบต่อไป แทนถือว่าพิกเซลอุปกรณ์ในภาพหน้าจอเป็นขนาด CSS

เทียบพื้นที่ระบายจริง

เราเรนเดอร์สัญลักษณ์ห้ากรณีใน Chrome ด้วย WebGL 2 แต่ละแผนที่มีหนึ่งจุด พื้นหลังอ่อนเดียวกัน ไม่มีไทล์และโทเค็น SVG ต้นทางทั้งคู่มีเส้นเดียวกันหนึ่งเส้นและไม่มีภาพฝัง

เพื่อวัดภาพดินเผา การอ่านของเราสแกนพิกเซล WebGL จริงหาสีแดงเด่น คือแดงมากกว่าเขียว 1.35 เท่าและน้ำเงิน 1.6 เท่า เราหารขนาดกรอบที่ตรวจพบด้วยอัตราส่วนบัฟเฟอร์ Canvas ต่อ 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 เพราะเลเยอร์พื้นหลังเองทึบ การเห็นพิกเซลแผนที่ทึบผ่านช่องไม่ได้แปลว่าไอคอนสูญเสียความโปร่งใส

นี่เป็นข้อสังเกตจำกัดตามต้นฉบับ เบราว์เซอร์ ค่า Canvas และเกณฑ์ของเรา ทดสอบภาพส่งที่ขนาดแสดงเป้าหมาย โดยเฉพาะเมื่อมีเส้นบางหรือช่องเล็ก

คัดลอกตัวอย่างสัญลักษณ์ไม่ใช้โทเค็น

บันทึก HTML ครบนี้และให้บริการจากเว็บเซิร์ฟเวอร์ในเครื่อง ต้องใช้อินเทอร์เน็ตสำหรับไลบรารีตรึงรุ่นกับ CSS แต่แผนที่ไม่ใช้บริการไทล์หรือ API key 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 เต็มสีทั่วไปก็ไม่กลายเป็น signed distance field เพียงเปลี่ยนแฟล็ก sdf ค่าความหนาแน่นควบคุมการตีความและแสดงภาพ ไม่ได้เปลี่ยนพิกเซลเป็นเส้นขอบแก้ไขได้

หากปลายทางต้องใช้ PNG คู่มือเรนเดอร์ SVG ด้วย Sharp ครอบคลุมการเตรียมสำเนาแรสเตอร์ส่งงาน เก็บ SVG ทั่วไปไว้แก้ไข SVG inline กับการส่งเป็นภาพ ก็มีรูปแบบโต้ตอบต่างกัน

หากไอคอนมีเพียง PNG หรือ JPG และต้องการภาพแก้ไขได้ ให้กู้เส้นขอบ SVG ด้วย PerfectVector ตรวจรูปนอกและช่อง แล้วเก็บต้นฉบับก่อนถอดรหัสภาพแผนที่ โค้ดแผนที่ยังควบคุมความหนาแน่น ขนาดสัญลักษณ์ และตำแหน่งภูมิศาสตร์ SVG สะอาดเดิมเข้าสู่ขั้นถอดรหัสได้โดยตรง คู่มือแปลงเวกเตอร์ อธิบายว่าเมื่อใดการแปลงแรสเตอร์มีประโยชน์

คำถามที่พบบ่อย

MapLibre GL JS แสดงภาพ SVG เป็นสัญลักษณ์ได้หรือไม่? ได้ ตัวอย่างทางการปัจจุบันถอดรหัสข้อความ SVG ด้วย Image ของเบราว์เซอร์และลงทะเบียนภาพสำหรับเลเยอร์สัญลักษณ์ เก็บ SVG ต้นฉบับแยกจากภาพลงทะเบียน

ทำไมอินพุตใหญ่ขึ้นทำให้ไอคอนใหญ่? เพิ่มขนาดถอดรหัสโดยคง image pixel ratio กับ icon-size จะเพิ่มกรอบตามกำหนด จับคู่ความหนาแน่นภาพกับสเกลแสดงโดยตั้งใจ

pixelRatio เก็บเส้น SVG ที่แก้ไขได้ในสัญลักษณ์หรือไม่? ไม่ มันอธิบายความหนาแน่นภาพ เก็บและแก้ SVG ต้นฉบับแล้วถอดรหัสภาพใหม่สำหรับแผนที่

ทำไมพิกเซลแผนที่ในช่องไอคอนจึงทึบ? เลเยอร์พื้นหลังทึบแสดงผ่านช่องโปร่งใสของไอคอนได้ ตรวจอัลฟาภาพลงทะเบียนแยกจาก Canvas แผนที่ที่ประกอบครบแล้ว

แหล่งข้อมูล

  1. MapLibre — แสดงสัญลักษณ์ SVG ระยะไกล — เส้นทางข้อความ SVG ภาพเบราว์เซอร์ และ addImage ปัจจุบัน
  2. MapLibre — Map API — วัตถุภาพที่รับและลงทะเบียนภาพขณะทำงาน
  3. MapLibre — StyleImageMetadata — เมทาดาทาความหนาแน่นและการตีความ SDF
  4. MapLibre — ข้อกำหนดเค้าโครงสัญลักษณ์ — การปรับขนาดไอคอนและพฤติกรรมชน
  5. MapLibre — MapOptions — ตัวเลือก Canvas ที่ใช้ควบคุมไฟล์วัด
  6. MDN — HTMLImageElement.decode — รอถอดรหัสภาพเบราว์เซอร์และจัดการความล้มเหลว
  7. MapLibre — เพิ่มไอคอนกำหนดเองด้วย Markers — ขั้นตอนหมุดองค์ประกอบ HTML แยก

เพิ่มเติมจากบล็อก

PerfectVector

เริ่มต้นด้วย SVG ที่เรียบร้อยขึ้น
และแก้ไขได้ง่ายกว่า

ไม่ต้องใช้บัตรเครดิต