いきなり日曜大工

好きなことを好きなだけやる

Leaflet マーカー編 #5 マーカーの重ね順をコントロール

コログデータ追加

コログのデータを追加しました。

BotW Demo #10 – コログデータ追加

当初ゲーム内地図と同じになるようにマーカーを置いてみましたが、web ブラウザでは見づらかったりマウスでアイコンを選択するのが厳しいものがあったので少々変更を加えました。

  • コログが表示されるようになるズームレベルをちょっと小さく設定(より俯瞰で見られる)
  • 他のアイコンと重なりすぎるコログ(3つ)についてほんのちょっと位置を調整
  • それでも見づらいので地図の最大ズームレベルの設定を1つ大きく

マーカーの重ね順をコントロール

これで常時表示させたいアイコンとデータが揃ったので(ゲーム内地図にはあと神獣がありますが考え中)、アイコンの種類ごとにマーカーの重ね順を設定し、マウスホバーしたアイコンは最前面に表示させるようにして本物に近づけます。

Leaflet でのマーカー重ね順の設定方法

アイコンの種類ごとにマーカーの重ね順を設定するには、アイコンの種類ごとにマーカーが入るペインを分けてペインにそれぞれ z-index を設定する方法と、同一ペイン内でアイコンの種類ごとにマーカーに z-index の加算値を設定する方法があります。

ペインで分ける

ペインの実体は div なので、ペインを分けるということはマーカーを違う div に入れるということになり、z-index はペインの div に対して指定します。ペインについては こちら の記事でも話題にしています。

// ペイン 'foo' を作る
map.createPane('foo');
/* クラス名は leaflet-foo-pane */
.leaflet-foo-pane {
  z-index: 500;
}

ペインの名前に対する CSS のクラス名は web ブラウザの開発ツールで実際に生成されたペイン div のクラス名を確認できます(具体的な命名ルールは Leaflet ソースの createPane の定義部分に)。

同一ペイン内で z-index (に加算する値)を設定する

同一ペインのマーカーは同じ div に入るので、各マーカーの z-index により重ね順が決まります。

L.Marker のオプション zIndexOffset はマーカーの z-index にこのオプションで指定した値を加算します。これを利用してアイコンの種類ごとに z-index に加算する値に差をつけることで並び順を設定します。

shrines.forEach(function(shrine) {
    L.marker(shrine.latLng, {
        icon: shrineIcon,
        zIndexOffset: 200    // z-index が +200 される
    }).addTo(map);
});

Leaflet の仕様(現在のバージョンは 1.5.1)によると、マーカーの画像の z-index のデフォルト値はそのマーカーの緯度に基づくとあります(参考:zIndexOffset)。実際にはマーカーの緯度から計算したピクセル座標の y をそのまま使っているようです(ソースを読むか web ブラウザの開発ツールでマーカー div の属性値を見てみるとわかります)。

L.Marker にオプション zIndexOffset を指定していると z-index の値はデフォルト(緯度に基づくピクセル値) + zIndexOffset になるので、このオフセットに指定する値の目安としてマーカーが地図上で他のマーカーと重なり合う可能性のある範囲のピクセル高より十分大きい値を設定します。

また、オプション riseOnHover を true にすると、そのマーカーをマウスホバーしている間は一時的に z-index に riseOffset の値(デフォルトは250)を加算することができます。

shrines.forEach(function(shrine) {
    L.marker(shrine.latLng, {
        icon: shrineIcon,
        zIndexOffset: 200,    // z-index が +200 される
        riseOnHover: true,
        riseOffset: 500       // マウスホバー中はさらに +500
    }).addTo(map);
});

このオプションに期待される効果はマウスホバーしたマーカーを最前面に持ってくることだと思われるので、同じペインの中で zIndexOffset を使っている場合はこれに負けない(加算すれば最前面に出せる)値を riseOffset に設定します。

zIndexOffset や riseOffset でマーカーの重ね順を変えられるのは同じペインに入っているマーカーの間でのみです。(参考:The stacking context

以上を踏まえまして、実際にマーカーの重ね順の設定をしてみます。

マーカーの重ね順を決める

ゲーム内地図の各アイコンの重ね順を観察して、これを参考に今まで追加したマーカーのアイコンの重ね順を決定します。

  1. シーカータワー
  2. 試練の祠(DLC)
  3. 試練の祠・古代研究所
  4. 町・馬宿・その他拠点
  5. コログ
  6. テキストラベル(地名・町)

最下位のテキストラベルはすでに別ペインを用意してペインごと背面に来るようにしてあるのでそのままです。その他のマーカーはこの順位でマーカーのアイコンを表示させて、マウスホバーの時は最前面に来るようにします。

L.Marker のオプションを設定

まずはアイコンの種類ごとに z-index に加算する値(zIndexOffset)の定義を作ります。それからマウスホバー時に z-index に一時加算される値(riseOffset)の値を決定します。riseOffset は各アイコンに設定する zIndexOffset の最大値より大きい値にします。

これらの値をマーカー生成時のオプションに指定していきます。

// アイコンの種類ごとに z-index に加算する値
const orderOffset = {
    'tower':      400,
    'shrine-dlc': 300,
    'shrine':     200,
    'lab':        200,
    'village':    100,
    'stable':     100,
    'settlement': 100
};
// マウスホバー時に z-index に一時加算される値
const riseOffset = 500;

const layers = {};
Object.keys(features).forEach(function(group) {
    layers[group] = L.layerGroup();
    Object.keys(features[group]).forEach(function(iconType) {
        const icon = spriteIcon(iconType);
        const zIndex = (iconType in orderOffset) ? orderOffset[iconType] : 0;
        for(let i=0,len=features[group][iconType].length; i<len; i++) {
            const feature = features[group][iconType][i];
            const tooltip = createTooltip(iconType, feature);
            const marker = L.marker(feature.coord, {
                icon: icon,
                riseOnHover: true,
                riseOffset: riseOffset,
                zIndexOffset: zIndex
            }).bindTooltip(tooltip);
            layers[group].addLayer(marker);
        }
    });
});

こんな感じでうまいこといったようです。意図したアイコンの重ね順になり、テキストラベル以外はマウスホバーで最前面に出るようになりました。

BotW Demo #11 – マーカーの重ね順をコントロール

これでゲーム内地図システムとだいたい同じになりました(神獣除く)。