LeafletのSVGマーカー:iconAnchorで先端を合わせる
SVGの見える先端をiconAnchorに対応させてLeafletの独自マーカーを配置。viewBoxの余白、縦横比のフィット、アイコンサイズの変更を考慮します。
目次
Leafletの独自SVGマーカーでは、iconSizeに表示画像の寸法を、iconAnchorに画像内の見える先端のピクセル位置を設定します。画像の左上から測ってください。SVGの透明な余白も寸法に含まれるため、非対称なピンや余白付きのピンでは、幅の半分と高さ全体の位置が先端から外れる場合があります。
このガイドでは、先端が本体の中心より右にある独自のピンを使います。タイルなしのブラウザー地図で、二つの元画像の枠を、二つのアイコンサイズで比較します。きれいなSVGアートワークがすでにある場合は、そのまま使ってください。アンカーの配置にベクター化は必要ありません。
アートワーク、画像の枠、地図上の点を分ける
ここでは三つの座標系が関わります。SVGのパスは元データの単位でアートワークを表します。iconSizeは表示する画像の枠をピクセルで定めます。Leafletは、その枠内のiconAnchorで指定した点を、マーカーの地理的位置に置きます。Iconリファレンスは、これらの設定と、アンカーの原点が左上であることを定義しています。
一般的なピンは下辺の中央まで伸びているかもしれませんが、自分のアートワークの終点は別の場所にある場合があります。Leafletの文書化された既定値は、サイズを指定した場合の中央アンカーです。この実験の下中央の値は、明示的にテストする仮定であり、既定値ではありません。
公式のカスタムアイコンチュートリアルは、マーカーにL.iconのインスタンスを渡す方法を示しています。読み込んだアートワークで追加で必要なのは、地図の座標に接するべき画像内の点を探す作業です。

元の先端をアイコンのピクセルに対応させる
このピンの先端は、元座標(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 80 | 60×80 | [40, 80] |
| 余白を抑える、0 0 60 80 | 90×120 | [60, 120] |
| 余白付き、-20 -10 100 120 | 60×80 | [36, 58] |
| 余白付き、-20 -10 100 120 | 90×120 | [54, 87] |
この計算は、変形のないこの画像と、明示的に中央に配置したmeet設定に適用されます。別の配置、slice、noneには、それぞれに合う座標対応が必要です。縦横比ガイドで、その選択肢を扱っています。マーカー画像に追加のCSS変形がある場合も、別に確認が必要です。
タイルなしの地図で配置を確認する
公式ダウンロードページで安定版として示されているLeaflet 1.9.4を使いました。この実験は、バージョンを固定したJavaScriptとCSSをローカルで読み込み、タイルレイヤーを加えません。各地図は座標[0, 0]を青い十字線に投影し、その基準点のそばに一つのSVG画像を表示します。
元データの確認では、各SVGに同一の複合パスが一つあり、埋め込みラスター画像はありませんでした。ブラウザーの画像寸法は、指定どおり60×80と90×120ピクセルでした。各画像の観察した長方形を使い、対応付けた先端位置と、投影された地理的な点を比較しました。
| 元データ | アイコンサイズ | 下中央と仮定したアンカーの誤差 | 計算した先端アンカーの誤差 |
|---|---|---|---|
| 余白を抑える | 60×80 | 右へ10 px | 0 px |
| 余白を抑える | 90×120 | 右へ15 px | 0 px |
| 余白付き | 60×80 | 右へ6 px、上へ22 px | 0 px |
| 余白付き | 90×120 | 右へ9 px、上へ33 px | 0 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リファレンスで、この規則を説明しています。この例は地図コンテナーに枠線や変形を加えません。それらを使う場合は、ビューポートの長方形とコンテナー座標を比較するときに注意が必要です。
アートワークを変える前に利用先を確認する
マーカーがまだ座標から外れる場合は、順に確認します。
- SVG画像が読み込まれ、マーカー要素が意図したピクセル幅と高さを持っていることを確認します。
- SVG内部の変形も含めて、実際の元の先端を特定します。
- viewBox全体、最小座標、フィット方法を確認します。
- 枠やアイコンサイズを変えるたびに、アンカーを再計算します。
- テスト中は無関係なCSSの上書きを外し、その後で最終スタイルと予定の表示サイズをもう一度確認します。
この確認中は、地理的な座標を固定してください。画像の余白を埋め合わせるためにその座標を動かすと、アートワークの配置ミスが隠れます。
これはブラウザーのマーカー用手順です。QGISのSVGマーカーには独自の利用先設定とパラメーター化された塗りの構文があります。一般的なブラウザーで使うために、通常のSVGマスターを保管してください。
アイコンがPNGやJPGでしか存在せず、編集可能な輪郭が必要なら、PerfectVectorでアートワークを変換し、開口部と先端を確認して、きれいなSVGマスターを保存します。その後、最終的な元画像の枠からLeafletのアンカーを計算してください。ベクター化はアートワークを準備し、地図コードが位置と配置を指定します。既存のSVGのずれだけが問題なら、枠またはアンカーを直接修正します。画像ベクター化ガイドで、ラスターの元画像の変換が役立つ場面を説明しています。
よくある質問
iconAnchorは緯度と経度ですか? 違います。表示アイコン画像内の、左上から測ったピクセル位置です。マーカーの地理的な位置は別に指定します。
アンカーを直しても、余白付きSVGが小さいのはなぜですか? 空の元画像の枠が、画像領域の一部を占めたままだからです。アンカーの補正は先端の位置を合わせるもので、余白を削除したり、塗られた図形を拡大したりしません。
常に幅の半分と高さ全体を使うべきですか? 見える接点が本当にその位置にある場合だけ、その組み合わせを使ってください。非対称なこのピンと余白付きの元画像には、どちらも位置マーカーの図柄でありながら、異なる値が必要です。
PNGをSVGに変換するとマーカーの位置は直りますか? 変換で編集可能なアートワークを用意できます。それでも、最終的な元画像の枠、表示寸法、アンカー、地図座標がマーカーの配置を決めます。
出典
- Leaflet — Iconリファレンス — ピクセル寸法、アンカーの原点、既定の中央配置、地理的な位置合わせ。
- Leaflet — 独自アイコンのマーカー — アイコンの作成とマーカーへの割り当て。
- Leaflet — ダウンロード — 安定版と、バージョン固定のJavaScriptおよびCSSリンク。
- MDN — viewBox — 二つの枠で使う元の座標領域。
- MDN — preserveAspectRatio — xMidYMid meetによる均一なフィットと余白の中央配置。
- MDN — fill-rule — 透明な開口部に使う偶奇規則。


