PerfectVector
Autor: Irene Kim

Znaczniki SVG Leaflet: wyrównaj czubek przez iconAnchor

Pozycjonuj znaczniki SVG w Leaflet, mapując czubek na iconAnchor. Uwzględnij marginesy viewBox, dopasowanie proporcji i zmiany rozmiaru ikony.

9 min czytania
Na tej stronie

Dla własnego znacznika SVG w Leaflet ustaw iconSize na wymiary wyświetlanego obrazu, a iconAnchor na pikselową pozycję widocznego czubka wewnątrz obrazu. Mierz od jego lewego górnego narożnika. Przezroczyste marginesy SVG wliczają się do wymiarów, więc połowa szerokości i pełna wysokość mogą nie trafić w asymetryczny znacznik lub taki z marginesami.

Poradnik używa naszego oryginalnego znacznika, którego czubek leży na prawo od środka korpusu. Porównujemy dwa kadry źródłowe w dwóch rozmiarach ikony na przeglądarkowej mapie bez kafelków. Jeśli masz już czystą grafikę SVG, użyj jej bezpośrednio; umieszczenie kotwicy nie wymaga wektoryzacji.

Oddziel grafikę, prostokąt obrazu i punkt mapy

Spotykają się tu trzy układy współrzędnych. Ścieżka SVG opisuje grafikę w jednostkach źródła. iconSize ustala wyświetlany prostokąt obrazu w pikselach. Leaflet umieszcza punkt wewnątrz prostokąta określony przez iconAnchor w geograficznym położeniu znacznika. Opis Icon definiuje te opcje i początek kotwicy w lewym górnym narożniku.

Tradycyjny znacznik może dochodzić do środka dolnej krawędzi, ale twoja grafika może kończyć się gdzie indziej. Udokumentowane domyślne zachowanie Leaflet to wyśrodkowana kotwica, gdy podano rozmiar. Wartości dolnego środka w naszym eksperymencie są jawnym testowanym założeniem, a nie tym ustawieniem domyślnym.

Oficjalny samouczek własnych ikon pokazuje przekazanie instancji L.icon do znacznika. Dodatkową pracą dla importowanej grafiki jest znalezienie punktu wewnątrz obrazu, który ma dotykać współrzędnej mapy.

Widoczny czubek i kotwica
Koncepcyjne porównanie asymetrycznego znacznika położenia w obszarze z marginesami, z czubkiem najpierw odsuniętym od celownika, a potem dotykającym go
Ilustracja: uwzględnij cały kadr obrazu podczas pozycjonowania widocznego czubka. Zmierzony eksperyment przeglądarkowy poniżej używa osobno przygotowanych SVG.

Przekształć czubek źródła na piksele ikony

Czubek naszego znacznika leży we współrzędnej źródłowej (40, 80). Jego ciasny kadr to viewBox="0 0 60 80". Wersja z marginesami zmienia tylko kadr na viewBox="-20 -10 100 120"; oba pliki zachowują tę samą ścieżkę i przezroczysty otwór.

Opis viewBox wyjaśnia ten prostokąt źródła. Puste miejsce wewnątrz niego podąża za grafiką. Nasz poradnik obwiedni SVG wyjaśnia, dlaczego obszar roboczy i pomalowany kształt mogą mieć inne granice.

Obie wersje jawnie używają preserveAspectRatio="xMidYMid meet". Dopasowuje to kadr równomiernie wewnątrz viewportu i centruje pozostałe miejsce. Zobacz opis preserveAspectRatio MDN.

Dla tego trybu dopasowania niech W i H będą wymiarami ikony, (minX, minY, vbW, vbH) viewBox, a (tipX, tipY) czubkiem źródłowym:

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

Dla ikony z marginesami 60×80 s wynosi 0.6. Kadr źródłowy 100×120 zajmuje 60×72 piksele, pozostawiając cztery piksele nad i pod nim. Czubek staje się [36, 58], łącznie z tym pionowym przesunięciem. Samo pobranie ułamka każdego wymiaru oddzielnie pominęłoby etap równomiernego dopasowania.

Kadr źródłowyRozmiar ikonyObliczona kotwica czubka
Ciasny, 0 0 60 8060×80[40, 80]
Ciasny, 0 0 60 8090×120[60, 120]
Z marginesami, -20 -10 100 12060×80[36, 58]
Z marginesami, -20 -10 100 12090×120[54, 87]

Obliczenia dotyczą naszego nieprzekształconego obrazu i jawnego wyśrodkowanego ustawienia meet. Inne wyrównanie, slice lub none wymaga odpowiedniego mapowania współrzędnych. Nasz poradnik proporcji opisuje te opcje. Dodatkowe transformacje CSS obrazu znacznika też wymagają osobnego sprawdzenia.

Sprawdź położenie na mapie bez kafelków

Użyliśmy Leaflet 1.9.4, stabilnego wydania wskazanego na oficjalnej stronie pobierania. Eksperyment wczytuje lokalnie przypięte JavaScript i CSS, bez warstwy kafelków. Każda mapa rzutuje współrzędną [0, 0] na niebieski celownik i wyświetla jeden obraz SVG obok punktu odniesienia.

Kontrola źródeł znalazła jedną identyczną ścieżkę złożoną i brak osadzonego rastra w każdym SVG. Wymiary obrazów w przeglądarce wynosiły 60×80 i 90×120 pikseli, zgodnie z żądaniem. Używając zaobserwowanego prostokąta każdego obrazu, porównaliśmy przekształconą pozycję czubka z rzutowanym punktem geograficznym:

ŹródłoRozmiar ikonyBłąd przy założonej kotwicy na dole pośrodkuBłąd przy obliczonej kotwicy czubka
Ciasne60×8010 px w prawo0 px
Ciasne90×12015 px w prawo0 px
Z marginesami60×806 px w prawo, 22 px w górę0 px
Z marginesami90×1209 px w prawo, 33 px w górę0 px

Celownik także wizualnie spotykał poprawione czubki. To obserwacje tego przygotowanego przykładu w jednej przeglądarce Chrome, a nie gwarancja dla każdego SVG, przeglądarki lub integracji mapy. Znacznik z marginesami nadal jest mniejszy w tym samym prostokącie obrazu po poprawieniu kotwicy. Położenie i rozmiar grafiki to odrębne decyzje.

Skopiuj działający przykład startowy znacznika

Zapisz ten kompletny HTML jako marker.html i udostępnij z lokalnego serwera internetowego. Wczytuje przypięte pliki Leaflet z CDN podlinkowanego na stronie pobierania Leaflet. SVG jest zakodowany w adresie obrazu, więc przykład nie wymaga pliku ikony ani usługi kafelków. Dostęp do internetu jest potrzebny do dwóch plików biblioteki.

<!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>

Wyświetlony stan powinien podać anchor: [36, 58], rozmiar obrazu [60, 80] i zerowe przemieszczenie. Zmień size na [90, 120]; to samo obliczenie daje [54, 87]. Aby użyć ciasnego kadru, zmień zarówno viewBox SVG, jak i tablicę JavaScript viewBox na 0 0 60 80. Zachowaj ich zgodność.

Ścieżka zawiera kołowy kontur wewnętrzny z fill-rule="evenodd", tworzący prawdziwy otwór. Opis fill-rule MDN wyjaśnia regułę. Przykład nie dodaje obramowania ani transformacji kontenera mapy; wymagałyby ostrożności przy porównywaniu prostokątów viewportu ze współrzędnymi kontenera.

Sprawdź miejsce docelowe przed zmianą grafiki

Jeśli znacznik nadal nie trafia we współrzędną, sprawdź kolejno:

  1. Potwierdź wczytanie SVG i zamierzoną szerokość oraz wysokość elementu znacznika w pikselach.
  2. Znajdź faktyczny czubek źródłowy, uwzględniając transformacje wewnątrz SVG.
  3. Sprawdź cały viewBox, jego minimalne współrzędne i tryb dopasowania.
  4. Przelicz kotwicę po każdej zmianie kadru lub rozmiaru ikony.
  5. Usuń niezwiązane nadpisania CSS podczas testu, a potem ponownie sprawdź końcowe style i docelowe rozmiary wyświetlania.

Zachowaj współrzędną geograficzną bez zmian podczas kontroli. Przesunięcie jej dla kompensacji marginesu obrazu ukryłoby błąd położenia grafiki.

To proces znacznika przeglądarkowego. Znacznik SVG w QGIS używa własnych kontrolek docelowych i parametryzowanej składni malowania; zachowaj zwykły wzorzec SVG do ogólnego przekazania przeglądarkowego.

Jeśli ikona istnieje tylko jako PNG lub JPG i potrzebujesz edytowalnych konturów, przekonwertuj grafikę w PerfectVector, sprawdź otwór i czubek oraz zapisz czysty wzorzec SVG. Następnie oblicz kotwicę Leaflet z końcowego kadru źródła. Wektoryzacja przygotowuje grafikę; kod mapy dostarcza lokalizacje i położenie. Jeśli jedynym problemem jest przesunięcie istniejącego SVG, popraw jego kadr lub kotwicę bezpośrednio. Poradnik wektoryzacji obrazu wyjaśnia, kiedy konwersja rastra jest przydatna.

Najczęstsze pytania

Czy iconAnchor to szerokość i długość geograficzna? Nie. To pikselowa pozycja wewnątrz wyświetlanego obrazu ikony, mierzona od lewego górnego narożnika. Geograficzna pozycja znacznika jest podawana oddzielnie.

Dlaczego SVG z marginesami nadal jest mały po poprawieniu kotwicy? Pusty kadr źródła nadal zajmuje część prostokąta obrazu. Poprawienie kotwicy pozycjonuje czubek; nie usuwa pustego miejsca ani nie powiększa pomalowanego kształtu.

Czy zawsze używać połowy szerokości i pełnej wysokości? Używaj tej pary tylko wtedy, gdy widoczny punkt zaczepienia faktycznie tam wypada. Nasz asymetryczny znacznik i źródło z marginesami wymagają innych wartości, mimo że oba są grafiką znaczników lokalizacji.

Czy konwersja PNG na SVG naprawia pozycję znacznika? Konwersja może dostarczyć edytowalną grafikę. Końcowy kadr źródła, wyświetlane wymiary, kotwica i współrzędne mapy nadal decydują o położeniu znacznika.

Źródła

  1. Leaflet — Opis Icon — Wymiary w pikselach, początek kotwicy, domyślne centrowanie i wyrównanie geograficzne.
  2. Leaflet — Znaczniki z własnymi ikonami — Tworzenie ikony i przypisanie jej do znacznika.
  3. Leaflet — Pobieranie — Stabilne wydanie i jego przypięte odnośniki JavaScript oraz CSS.
  4. MDN — viewBox — Prostokąt współrzędnych źródłowych użyty w dwóch kadrach.
  5. MDN — preserveAspectRatio — Równomierne dopasowanie i wyśrodkowane pozostałe miejsce dla xMidYMid meet.
  6. MDN — fill-rule — Reguła parzyste-nieparzyste użyta dla przezroczystego otworu.

Więcej z bloga

PerfectVector

Zacznij od czystszego SVG,
który łatwiej edytować

Bez karty kredytowej