PerfectVector
著者: Irene Kim

LeafletのSVGマーカー:iconAnchorで先端を合わせる

SVGの見える先端をiconAnchorに対応させてLeafletの独自マーカーを配置。viewBoxの余白、縦横比のフィット、アイコンサイズの変更を考慮します。

3 分で読める
目次

Leafletの独自SVGマーカーでは、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"を明示的に使います。枠をビューポート内に均一な倍率で収め、余った空間を中央に配置します。設定については、MDNのpreserveAspectRatioリファレンスを参照してください。

このフィット方法で、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ピクセルを占め、上下に4ピクセルずつ残ります。先端は、その垂直方向のオフセットを含めて[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]になります。余白を抑えた枠を使う場合は、SVGのviewBoxとJavaScriptのviewBox配列を、両方とも0 0 60 80に変更します。両者を一致させてください。

パスにはfill-rule="evenodd"の円形の内側輪郭が含まれ、本当の開口部を作ります。MDNのfill-ruleリファレンスで、この規則を説明しています。この例は地図コンテナーに枠線や変形を加えません。それらを使う場合は、ビューポートの長方形とコンテナー座標を比較するときに注意が必要です。

アートワークを変える前に利用先を確認する

マーカーがまだ座標から外れる場合は、順に確認します。

  1. SVG画像が読み込まれ、マーカー要素が意図したピクセル幅と高さを持っていることを確認します。
  2. SVG内部の変形も含めて、実際の元の先端を特定します。
  3. viewBox全体、最小座標、フィット方法を確認します。
  4. 枠やアイコンサイズを変えるたびに、アンカーを再計算します。
  5. テスト中は無関係なCSSの上書きを外し、その後で最終スタイルと予定の表示サイズをもう一度確認します。

この確認中は、地理的な座標を固定してください。画像の余白を埋め合わせるためにその座標を動かすと、アートワークの配置ミスが隠れます。

これはブラウザーのマーカー用手順です。QGISのSVGマーカーには独自の利用先設定とパラメーター化された塗りの構文があります。一般的なブラウザーで使うために、通常の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から始めましょう

クレジットカード不要