PerfectVector
โดย Irene Kim

หมุด SVG ใน Leaflet: จัดปลายด้วย iconAnchor

วางหมุด SVG ใน Leaflet โดยแมปปลายที่เห็นกับ iconAnchor คำนึงถึงขอบว่าง viewBox การปรับสัดส่วน และการเปลี่ยนขนาดไอคอน

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

สำหรับหมุด SVG กำหนดเองใน Leaflet ตั้ง iconSize เป็นขนาดภาพที่แสดง และ iconAnchor เป็นตำแหน่งพิกเซลของปลายที่เห็นภายในภาพ วัดจากมุมบนซ้ายของภาพ ขอบโปร่งใส SVG นับรวมในขนาดด้วย ดังนั้นครึ่งความกว้างกับความสูงเต็มอาจไม่ตรงปลายหมุดที่ไม่สมมาตรหรือมีขอบว่าง

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

แยกภาพ กรอบภาพ และจุดแผนที่

มีระบบพิกัดสามชุดมาพบกัน เส้น SVG อธิบายภาพในหน่วยต้นฉบับ iconSize กำหนดกรอบภาพที่แสดงเป็นพิกเซล Leaflet วางจุดในกรอบที่ iconAnchor ระบุไว้ตรงตำแหน่งภูมิศาสตร์ของหมุด เอกสาร Icon นิยามตัวเลือกเหล่านี้และจุดเริ่มยึดที่มุมบนซ้าย

หมุดทั่วไปอาจไปถึงกลางขอบล่าง แต่ภาพของคุณอาจสิ้นสุดที่อื่น ค่าเริ่มต้นตามเอกสาร Leaflet คือจุดยึดกลางเมื่อระบุขนาด ค่ากลางล่างในทดลองของเราเป็นสมมติฐานชัดเจนที่ทดสอบ ไม่ใช่ค่าเริ่มต้นนั้น

บทเรียนไอคอนกำหนดเองทางการ แสดงวิธีส่งอินสแตนซ์ L.icon ให้หมุด งานเพิ่มสำหรับภาพนำเข้าคือหาจุดภายในภาพที่ควรแตะพิกัดแผนที่

ปลายที่เห็นและจุดยึด
การเทียบเชิงแนวคิดของหมุดตำแหน่งไม่สมมาตรในพื้นที่มีขอบว่าง โดยปลายห่างจากกากบาทก่อน แล้วมาพบกากบาท
ภาพอธิบาย: คำนึงถึงกรอบภาพทั้งหมดเมื่อวางปลายที่เห็น การทดลองเบราว์เซอร์ที่วัดด้านล่างใช้ SVG ที่สร้างแยก

แมปปลายต้นฉบับเป็นพิกเซลไอคอน

ปลายหมุดของเราอยู่พิกัดต้นฉบับ (40, 80) กรอบกระชับคือ viewBox="0 0 60 80" เวอร์ชันเผื่อพื้นที่เปลี่ยนเฉพาะกรอบเป็น viewBox="-20 -10 100 120" ทั้งสองเก็บเส้นเดิมและช่องโปร่งใส

เอกสาร viewBox อธิบายสี่เหลี่ยมต้นทางนี้ พื้นที่ว่างภายในเดินไปกับภาพ คู่มือกรอบขอบเขต SVG อธิบายว่าทำไมพื้นที่งานกับรูปที่ระบายมีขอบเขตต่างกันได้

ทั้งสองระบุ preserveAspectRatio="xMidYMid meet" ชัดเจน เพื่อปรับกรอบสม่ำเสมอภายใน viewport และจัดกลางพื้นที่เหลือ ดูเอกสาร preserveAspectRatio ของ MDN สำหรับค่านี้

ในโหมดนี้ ให้ W และ H เป็นขนาดไอคอน (minX, minY, vbW, vbH) เป็น viewBox และ (tipX, tipY) เป็นปลายต้นฉบับ:

s = min(W / vbW, H / vbH)
offsetX = (W - vbW * s) / 2
offsetY = (H - vbH * s) / 2
anchorX = (tipX - minX) * s + offsetX
anchorY = (tipY - minY) * s + offsetY

สำหรับไอคอนเผื่อพื้นที่ 60×80 ค่า s คือ 0.6 กรอบต้นฉบับ 100×120 กินพื้นที่ 60×72 พิกเซล เหลือบนล่างด้านละสี่ ปลายกลายเป็น [36, 58] รวมระยะเยื้องแนวตั้งนั้น การใช้เพียงสัดส่วนแต่ละมิติแยกกันจะพลาดขั้นปรับสม่ำเสมอ

กรอบต้นฉบับขนาดไอคอนจุดยึดปลายที่คำนวณ
กระชับ 0 0 60 8060×80[40, 80]
กระชับ 0 0 60 8090×120[60, 120]
เผื่อพื้นที่ -20 -10 100 12060×80[36, 58]
เผื่อพื้นที่ -20 -10 100 12090×120[54, 87]

สูตรนี้ใช้กับภาพที่ไม่แปลงรูปของเราและค่า meet จัดกลางชัดเจน การจัดแนวอื่น slice หรือ none ต้องใช้การแมปพิกัดตรงกัน คู่มือสัดส่วน ครอบคลุมตัวเลือก การแปลง CSS เพิ่มบนภาพหมุดต้องตรวจแยกด้วย

ตรวจตำแหน่งในแผนที่ไม่มีไทล์

เราใช้ Leaflet 1.9.4 รุ่นเสถียรที่ระบุในหน้าดาวน์โหลดทางการ การทดลองโหลด JavaScript และ CSS ที่ตรึงรุ่นในเครื่อง และไม่เพิ่มเลเยอร์ไทล์ แผนที่แต่ละอันฉายพิกัด [0, 0] เป็นกากบาทน้ำเงิน และแสดงภาพ SVG หนึ่งภาพข้างจุดอ้างอิง

การตรวจต้นฉบับพบเส้นประกอบเดียวกันหนึ่งเส้นและไม่มีแรสเตอร์ฝังตัวในแต่ละ SVG ขนาดภาพในเบราว์เซอร์เป็น 60×80 กับ 90×120 พิกเซลตามขอ เราใช้กรอบภาพที่สังเกตเทียบตำแหน่งปลายหลังแมปกับจุดภูมิศาสตร์ที่ฉาย:

ต้นฉบับขนาดไอคอนคลาดเคลื่อนเมื่อสมมติยึดกลางล่างคลาดเคลื่อนเมื่อคำนวณจุดยึดปลาย
กระชับ60×80ขวา 10 px0 px
กระชับ90×120ขวา 15 px0 px
เผื่อพื้นที่60×80ขวา 6 px ขึ้น 22 px0 px
เผื่อพื้นที่90×120ขวา 9 px ขึ้น 33 px0 px

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

คัดลอกตัวอย่างหมุดที่ทำงานได้

บันทึก HTML ครบนี้เป็น marker.html และให้บริการจากเว็บเซิร์ฟเวอร์ในเครื่อง มันโหลดไฟล์ Leaflet ที่ตรึงรุ่นจาก CDN ที่หน้าดาวน์โหลด Leaflet เชื่อมไว้ SVG เข้ารหัสเป็น URL ภาพ จึงไม่ต้องมีไฟล์ไอคอนหรือบริการไทล์ ต้องใช้อินเทอร์เน็ตสำหรับไฟล์ไลบรารีสองไฟล์นั้น

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Leaflet SVG tip anchor</title>
<link rel="stylesheet"
      href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<style>
  #map { width: 360px; height: 300px; background: #edf3ef; }
  .cross { position: absolute; z-index: 1000; pointer-events: none;
    width: 24px; height: 24px; transform: translate(-50%, -50%); }
  .cross::before, .cross::after { content: ""; position: absolute;
    background: #006cbe; }
  .cross::before { top: 12px; width: 24px; height: 1px; }
  .cross::after { left: 12px; width: 1px; height: 24px; }
</style>
<div id="map"></div>
<pre id="status">Loading marker…</pre>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="60" height="80" viewBox="-20 -10 100 120" preserveAspectRatio="xMidYMid meet"><path fill="#bf593c" fill-rule="evenodd" d="M30 0 C46 0 60 12 60 28 C60 46 46 54 40 80 C30 62 0 46 0 28 C0 12 14 0 30 0 Z M40 26 A10 10 0 1 0 20 26 A10 10 0 1 0 40 26 Z"/></svg>`;
const size = [60, 80];
const viewBox = [-20, -10, 100, 120];
const sourceTip = [40, 80];
const scale = Math.min(size[0] / viewBox[2], size[1] / viewBox[3]);
const anchor = [
  (sourceTip[0] - viewBox[0]) * scale
    + (size[0] - viewBox[2] * scale) / 2,
  (sourceTip[1] - viewBox[1]) * scale
    + (size[1] - viewBox[3] * scale) / 2
];
const map = L.map("map", {
  zoomControl: false, attributionControl: false,
  dragging: false, scrollWheelZoom: false, doubleClickZoom: false,
  boxZoom: false, keyboard: false, touchZoom: false
}).setView([0, 0], 2);
const position = [0, 0];
const icon = L.icon({
  iconUrl: "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg),
  iconSize: size,
  iconAnchor: anchor
});
const marker = L.marker(position, { icon }).addTo(map);
const container = document.querySelector("#map");
const cross = document.createElement("span");
cross.className = "cross";
container.append(cross);
const point = map.latLngToContainerPoint(position);
cross.style.left = point.x + "px";
cross.style.top = point.y + "px";
(async () => {
  const image = marker.getElement();
  await image.decode();
  const ir = image.getBoundingClientRect();
  const mr = container.getBoundingClientRect();
  const displacement = [
    ir.left + anchor[0] - mr.left - point.x,
    ir.top + anchor[1] - mr.top - point.y
  ];
  document.querySelector("#status").textContent = JSON.stringify({
    version: L.version, size, anchor,
    observedImageSize: [ir.width, ir.height], displacement
  }, null, 2);
})();
</script>
</html>

สถานะที่แสดงควรรายงาน anchor: [36, 58] ขนาดภาพ [60, 80] และระยะคลาดศูนย์ เปลี่ยน size เป็น [90, 120] การคำนวณเดิมให้ [54, 87] หากใช้กรอบกระชับ ให้เปลี่ยนทั้ง viewBox ใน SVG และอาร์เรย์ JavaScript viewBox เป็น 0 0 60 80 คงสองจุดให้ตรงกัน

เส้นมีขอบวงกลมด้านในกับ fill-rule="evenodd" สร้างช่องจริง เอกสาร fill-rule ของ MDN อธิบายกฎ ตัวอย่างไม่เพิ่มขอบหรือการแปลงให้ตัวครอบแผนที่ หากเพิ่มต้องระวังเมื่อเทียบกรอบ viewport กับพิกัดตัวครอบ

ตรวจปลายทางก่อนเปลี่ยนภาพ

หากหมุดยังไม่ตรงพิกัด ให้ตรวจตามลำดับ:

  1. ยืนยันว่า SVG โหลดและองค์ประกอบหมุดมีความกว้างสูงพิกเซลที่ต้องการ
  2. หาปลายต้นฉบับจริง รวมการแปลงภายใน SVG
  3. ตรวจ viewBox เต็ม พิกัดต่ำสุด และโหมดปรับภาพ
  4. คำนวณจุดยึดใหม่ทุกครั้งที่เปลี่ยนกรอบหรือขนาดไอคอน
  5. ลบ CSS ที่เขียนทับและไม่เกี่ยวข้องขณะทดสอบ แล้วตรวจสไตล์สุดท้ายกับขนาดแสดงเป้าหมายอีกครั้ง

คงพิกัดภูมิศาสตร์ไว้ระหว่างตรวจ การย้ายพิกัดชดเชยขอบภาพจะซ่อนข้อผิดพลาดวางภาพ

นี่เป็นขั้นตอนหมุดเบราว์เซอร์ หมุด SVG ใน QGIS ใช้ตัวควบคุมปลายทางและไวยากรณ์สีพารามิเตอร์ของตน เก็บต้นฉบับ SVG ทั่วไปสำหรับส่งเบราว์เซอร์ทั่วไป

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

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

iconAnchor คือพิกัดละติจูดลองจิจูดหรือไม่? ไม่ เป็นตำแหน่งพิกเซลภายในภาพไอคอนที่แสดง วัดจากมุมบนซ้าย ตำแหน่งภูมิศาสตร์หมุดให้แยกกัน

ทำไม SVG เผื่อพื้นที่ยังเล็กหลังแก้จุดยึด? กรอบว่างต้นฉบับยังครอบครองส่วนหนึ่งของกรอบภาพ การแก้จุดยึดวางปลาย ไม่ได้ลบพื้นที่ว่างหรือขยายรูปที่ระบาย

ควรใช้ครึ่งความกว้างกับความสูงเต็มเสมอหรือไม่? ใช้คู่นั้นเฉพาะเมื่อจุดแตะที่เห็นอยู่ตรงนั้นจริง หมุดไม่สมมาตรกับต้นฉบับเผื่อพื้นที่ของเราต้องการค่าต่างกัน แม้ทั้งคู่เป็นภาพหมุดตำแหน่ง

แปลง PNG เป็น SVG แก้ตำแหน่งหมุดหรือไม่? การแปลงให้ภาพแก้ไขได้ แต่กรอบต้นฉบับสุดท้าย ขนาดแสดง จุดยึด และพิกัดแผนที่ยังเป็นตัวกำหนดตำแหน่ง

แหล่งข้อมูล

  1. Leaflet — เอกสาร Icon — ขนาดพิกเซล จุดเริ่มยึด การจัดกลางเริ่มต้น และการจัดแนวภูมิศาสตร์
  2. Leaflet — หมุดไอคอนกำหนดเอง — สร้างไอคอนและกำหนดให้หมุด
  3. Leaflet — ดาวน์โหลด — รุ่นเสถียรและลิงก์ JavaScript กับ CSS ที่ตรึงรุ่น
  4. MDN — viewBox — สี่เหลี่ยมพิกัดต้นทางที่ใช้ในสองกรอบ
  5. MDN — preserveAspectRatio — การปรับสม่ำเสมอและพื้นที่เหลือจัดกลางสำหรับ xMidYMid meet
  6. MDN — fill-rule — กฎคู่คี่สำหรับช่องโปร่งใส

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

PerfectVector

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

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