Leaflet.jsで指定した範囲のみ閲覧可能な地図を作ってみる

# まえがき

ひょっとしたら世の中的にはそこそこニーズがあるかもしれないお話。

# 要望

たとえば、ある町で食べられるグルメ情報をピン等で可視化した地図を Leaflet.js で作成したとする。

ただ、掲載している情報の都合上、その町以外の場所を地図に表示させたくない。

この要望を満たすためにはどうしたらいいか？

# サンプルその1 「地図を動かしたら強制的に指定した位置に戻す」処理の実装

ということで、要望を満たす地図の挙動として、地図を動かすことで強制的に指定した座標に戻すようにしてみる。

コードとしてはこんな感じ。（[サンプルその1・ソース](https://gist.github.com/yuskesuzki/f333ed41118714e5961a198132bb0e2a#file-sample1-html)）


```js:sample1.html
        // 地図の中心緯度経度初期値
        var mapInitLatLng = [
            43.10379, 141.53621
        ];
        // 地図を定義
        var mymap = L.map('map', {
            maxZoom: 24,
            minZoom: 10,
        }).setView(mapInitLatLng, 16);

        // 地理院地図のタイルをセット
        L.tileLayer(
            'http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', {
            attribution: 'Map data &copy; <a href="http://maps.gsi.go.jp/">地理院地図</a>',
            maxZoom: 18,
        }).addTo(mymap);

        // 地図移動時に発生させるイベントをセット
        mymap.on('moveend', moveInitLatLng);

        /**
         * 地図の中心位置を mapInitLatLng に強制的に移動する
         */
        function moveInitLatLng(e) {
            mymap.panTo(mapInitLatLng);
        }
```

やっていることは、

- 地図の移動完了時に発火される moveend イベントに対し、関数を設定。
- moveend イベントに結びつけた関数で、 L.map オブジェクトの panTo メソッドを用い、指定した座標に地図の中心を設定する。

というもの。

このとき地図を表示させると、地図をドラッグ＆ドロップ等行って動かすたびに、地図の初期位置に戻る地図となる。

![スクリーンショット 2016-06-21 21.20.45.tiff](https://qiita-image-store.s3.amazonaws.com/0/18599/d6a40440-cea4-646a-c843-c2480d8da2aa.tiff)

![スクリーンショット 2016-06-21 21.16.45.png](https://qiita-image-store.s3.amazonaws.com/0/18599/4c07b355-553c-5ae9-32fc-0f9d3e1b0393.png)


# サンプルその2：地図を動かせる範囲を矩形で可視化する

サンプルその１の状態では単なる使い勝手の悪い地図なので、もうちょい融通をきかせてみる。

その処理を追加する前に、地図を動かせる範囲を決めて可視化してみる。（[サンプルその2・ソース](https://gist.github.com/yuskesuzki/f333ed41118714e5961a198132bb0e2a#file-sample2-html)）

```js:sample2.html
        // 市全体を囲む矩形を定義
        var cityBounds = [[43.17293, 141.44430], [43.01615, 141.6684]];
        var cityRect = L.rectangle(cityBounds, {color: "red", fillColor: '#f03', fillOpacity: 0.5, weight: 1});
        cityRect.addTo(mymap);
```

![スクリーンショット 2016-06-21 21.22.24.png](https://qiita-image-store.s3.amazonaws.com/0/18599/0773375c-da2a-0892-7046-6c2ccdacb03f.png)

これは単に地図上に矩形を描いてるだけです。しかし、こうして市全体を囲む矩形を描いてみると、わが町って広いな。

# サンプルその3 矩形の範囲内で地図を動かせるようにしてみる

サンプルその2で描いた矩形の範囲内だけ、地図を動かせるようにしてみる。（[サンプルその3・ソース](https://gist.github.com/yuskesuzki/f333ed41118714e5961a198132bb0e2a#file-sample3-html)）

```js:sample3.html
        // 地図移動時のイベントをセット
        mymap.on('moveend', moveInsideCityBound);

        /**
         * 地図の中心位置を市全体を囲む矩形内に移動する
         */
        function moveInsideCityBound(e) {
            // 表示してる地図の中心座標を取得
            var mapCenter = mymap.getCenter();
            // 市全体を囲む矩形の LatLngBounds オブジェクトを取得する
            var checkBounds = cityRect.getBounds();

            // 現在の地図の中心位置が checkBounds に含まれてるかチェック。
            // 含まれてない場合、地図の中心座標を矩形内部に戻す。
            if(!checkBounds.contains(mapCenter)) {
                mymap.panTo(mapInitLatLng);
            }

        }
```

やってることは簡単で、地図の中心座標が矩形の範囲に含まれてるかどうかチェックし、範囲からはみ出てる場合に戻すべき座標に戻す、というもの。

![スクリーンショット 2016-06-21 21.24.51.png](https://qiita-image-store.s3.amazonaws.com/0/18599/e81e93f7-b6ed-7166-8bb7-d4b4f80d6a45.png)

![スクリーンショット 2016-06-21 21.25.04.png](https://qiita-image-store.s3.amazonaws.com/0/18599/c4bee922-9323-b78f-572e-fe9c2b015f16.png)

ということで、とりあえず指定範囲まで閲覧できる地図ができた。

# ToDo

ただし、ここまでで戻す座標は地図の初期位置で固定にしてるので、動かせる範囲が広範囲に及ぶ場合、ユーザビリティがよろしくない。

なので、もうすこし融通を効かせて「矩形からはみ出した座標から、最も近い矩形内の座標に戻す」などとするとよさそう。

だけども、それはここまで読んでいただいた皆さんの宿題ということで。やり方はいろいろあると思います。

# おわりに

ところで、こういった制限を地図に与えるプラグインはひょっとしたら既にあるかもしれない。探してないけども。

# 追記

コメント欄でご指摘いただいた通り、Leaflet.jsでは L.Map.setMaxBounds メソッドを用いることで表示範囲に制限を設定できる。こちらが標準のやり方となります。tom_konda 様、コメントありがとうございました。

[サンプルその4・ソース](https://gist.github.com/yuskesuzki/f333ed41118714e5961a198132bb0e2a#file-sample4-html)

```js:sample4.html
        // 地図の中心緯度経度
        var mapInitLatLng = [
            43.10379, 141.53621
        ];
        // 地図を定義
        var mymap = L.map('map', {
            maxZoom: 24,
            minZoom: 10,
        }).setView(mapInitLatLng, 11);
        // 市全体を囲む矩形を定義
        var cityBounds = [[43.17293, 141.44430], [43.01615, 141.6684]];
        var cityRect = L.rectangle(cityBounds, {color: "red", fillColor: '#f03', fillOpacity: 0.5, weight: 1});
        cityRect.addTo(mymap);
        // setMaxBounds を指定して地図の表示範囲を制限
        mymap.setMaxBounds(cityBounds);
```