いきなり日曜大工

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

Leaflet マーカー編 #3 L.LayerGroup でグループ化・表示制御

L.LayerGroup でマーカーをグループ化

いっぱいおけるようになった

前回でアイコンがあらかた揃いましたが、データ作成の方も作ったアイコンの分についてはできたので、さっそくマーカーをがーっと置いてみます。

BotW Demo #6 – 作ったデータ全部置いてみる

しかし実はマーカーを全部同時に置いてはいけないのでした。BotW のゲーム内地図システムでは、ズームレベルが小さいときは町のアイコンが表示され、ズームレベルを上げると町のアイコンが消えて代わりに町の中のお店が表示される仕組みになっています。

ゲーム内地図システムのカカリコ村(町アイコンのみ)
ゲーム内地図システムのカカリコ村(店アイコンのみ)

これと同じ動きにするには Leaflet でズーム変更させたらそのタイミングでマーカーの表示・非表示をコントロールする必要があります。今回はたくさんになったマーカーのグループ化と、それをズームレベル変更に応じて表示・非表示させるという機能を作ります。

マーカーのグループ化

まずはマーカーを表示させたい単位ごとにグループ化してその後の処理で扱いやすくします。 マーカーのグループ化には L.LayerGroup というクラスを使います。

L.LayerGroup は、複数のレイヤーをまとめて一つのレイヤーであるがごとく扱えるという便利なクラスです。地図コンテナへの追加・削除は単一のレイヤーと同じようにできて、かつグループに対しての処理のメソッドももっています。

前回マーカー作成に使ったデータはアイコンごとの配列にまとめていました。

const features = {
    "castle": [
        { "name": "ハイラル城",   "coord": [-68.5714221, 89.7825013] }
    ],
    "village": [
        { "name": "カカリコ村",   "coord": [-93.5109145, 121.9702149] },
        { "name": "ハテノ村",     "coord": [-111.2804698, 149.8845814] },
        ...
    ]
};

今回は表示させたいグループでの区分を追加します。

const features = {
    // 表示単位:町(町のアイコン)
    "villages": {
        "village": [
            { "name": "カカリコ村",   "coord": [-93.5109145, 121.9702149] },
            { "name": "ハテノ村",     "coord": [-111.2804698, 149.8845814] },
            ...,
        ],
    },
    // 表示単位:店(よろず屋、宿屋、防具屋、宝飾屋、染色屋のアイコン)
    "shops": {
        "shop": [
            ...
        ],
        "armor": [
            ...
        ],
        ...
    },
    ...
};

では L.LayerGroup でマーカーをグループ化します。

let layers = {};
Object.keys(features).forEach(function(group) {
    // レイヤーグループを作成
    layers[group] = L.layerGroup();
    Object.keys(features[group]).forEach(function(icon_type) {
        let icon = spriteIcon(icon_type);
        for(let i=0,len=features[group][icon_type].length; i<len; i++) {
            let feature = features[group][icon_type][i];
            let marker = spriteMarker(feature.coord, icon, feature.name);
            // レイヤーグループに追加
            layers[group].addLayer(marker);
        }
    });
});

地図コンテナへの追加・削除は普通のレイヤーと同じように扱うことができます。

map.addLayer(layers['villages']);
map.removeLayer(layers['villages']);

L.LayerGroup をズームレベルで表示制御する

ではこのグループを使ってズームレベルに応じた表示のコントロールをします。

イベント処理

map が L.Map オブジェクトのとき、L.Map が発火するイベントのハンドリングは以下のように書けます。

map.on('click', function(e) {
    alert(e.latlng);
} );

イベント zoomend + L.LayerGroup

ここではズームレベル変更後の処理をしたいので、ズームレベル変更後に L.Map が発火するイベント zoomend にフックさせた関数内でズームレベルを取得して、それを見てレイヤーグループの出し入れをします(ソースを見た感じ hasLayer の判定はなくても問題ないみたいだけど一応)。

// ズームレベルに応じてレイヤーグループを表示・非表示にする
controlLayers = function(zoom) {
    if (zoom < 7) {
        // layers['shops'] が表示中なら削除
        if (map.hasLayer(layers['shops'])) {
            map.removeLayer(layers['shops']);
        }
        // layers['village'] が非表示なら表示
        if (!map.hasLayer(layers['villages'])) {
            map.addLayer(layers['villages']);
        }
    } else {
        // layers['village'] が表示中なら削除
        if (map.hasLayer(layers['villages'])) {
            map.removeLayer(layers['villages']);
        }
        // layers['shops'] が非表示なら表示
        if (!map.hasLayer(layers['shops'])) {
            map.addLayer(layers['shops']);
        }
    }
};

// 常時表示させるレイヤーグループを地図に追加
map.addLayer(layers['shrines']);
map.addLayer(layers['places']);

// 以下はズームレベルを見て表示切替
map.whenReady(function() {        // 地図の初期化後
    let zoom = map.getZoom();
    controlLayers(zoom);
});
map.on('zoomend', function() {    // ズームレベル変更後
    let zoom = map.getZoom();
    controlLayers(zoom);
});

BotW Demo #7 – イベント zoomend + L.LayerGroup

意図した通りにマーカーが切り替え表示されるようになりました。いい感じ!