leaflet.jsの日本語チュートリアルをやってみた。

`leaflet.js`を業務で使う事になったので調査。

埼玉大学谷謙二研究室さんが公開してくれている日本語のチュートリアル( http://ktgis.net/service/leafletlearn/index.html )が分かりやすかったので、やってみた。

なお、公式チュートリアル( https://leafletjs.com/examples.html )も存在するが、今回は触れてない。

```js:learn_leaflet.html

<!DOCTYPE html>
<html>

    <head>
        <meta charset="UTF-8">
        <title>埼玉大学谷謙二研究室さんが公開してくれているleaflet.jsの日本語チュートリアルをやってみた。</title>
        <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.0/dist/leaflet.css" />
        <script src="https://unpkg.com/leaflet@1.3.0/dist/leaflet.js"></script>

        <style>
            /*紫色のアイコンのCSS  */
            .icon1 {
                width: 20px !important;
                height: 20px !important;
                border-radius: 10px;
                border: 3px solid #fdfdfd;
                box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.8);
                background-color: rgb(204, 51, 166);
            }

            /*茶色のアイコンのCSS  */
            .icon2 {
                width: 20px !important;
                height: 20px !important;
                border-radius: 10px;
                border: 3px solid #fdfdfd;
                box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.8);
                background-color: rgb(204, 125, 51);
            }

            /* 作成するdivのCSS */
            .infostyle {
                border: solid 1px;
                background-color: azure;
                border-radius: 10px;
                opacity: 0.8;
                padding: 5px;
                font-size: 15px;
                color: black;
            }
        </style>

        <script>
            // 定数：地図URL
            const MAP_URL_STD    = 'https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png';  // 地理院地図・標準地図タイル
            const MAP_URL_PALE   = 'http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png';  // 地理院地図・淡色地図タイル
            const MAP_URL_OPEN   = 'http://tile.openstreetmap.jp/{z}/{x}/{y}.png';  // オープンストリートマップのタイル
            const MAP_URL_RELIEF = 'http://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png';  // 地理院地図・色別標高図タイル
            const MAP_URL_HILL   = 'http://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png';  // 地理院地図・陰影起伏図タイル

            // 定数：地図URLに紐づくaタグの属性
            const ATTR_URL_STD  = "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>";  // 地理院地図
            const ATTR_URL_PALE = "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>地理院タイル</a>";  // 地理院地図
            const ATTR_URL_OPEN = "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors";  // オープンストリートマップ

            // 定数：座標
            const COORD_BASE              = [35.40     , 136];  // 初期位置
            const COORD_SAITAMA_UNIV      = [35.8627   , 139.6072];  // 埼玉大学
            const COORD_SAKURA_KUYAKUSHO  = [35.8561   , 139.6098];  // 桜区役所
            const COORD_KITAURAWA_STA     = [35.871986 , 139.645908];  // 北浦和駅
            const COORD_ROUTE             = [[35.865465, 139.60734], [35.870404, 139.6249], [35.870195, 139.6320], [35.871047, 139.6447], COORD_KITAURAWA_STA];  // 北浦和駅～埼玉大学の道案内
            const COORD_SAITAMA_UNIV_SITE = [[35.864891, 139.605503], [35.865969, 139.6088], [35.865378, 139.6097], [35.863309, 139.609559], [35.858996, 139.609709], [35.858248, 139.608722], [35.859813, 139.6053], [35.864248, 139.6056], [35.864891, 139.605503]];  // 埼玉大学の敷地
            const COORD_STOREs = [  // コンビニ一覧
                { pos: [35.8645472, 139.6048663], name: "セブンイレブン浦和埼玉大学店" },
                { pos: [35.8689857, 139.6086909], name: "セブンイレブンさいたま大久保店" },
                { pos: [35.871305, 139.6128431], name: "ファミリーマート浦和上大久保店" },
                { pos: [35.8665389, 139.6133905], name: "ミニストップさいたま上大久保店" },
                { pos: [35.8650306, 139.6070633], name: "ローソン埼玉大学店" }
            ];

            // クラス：オーバレイするグリッド線(タイル座標(z,x,y))をGridLayerで表示するためのクラス
            var GridLayerClass = L.GridLayer.extend({
                createTile: function (coords) {
                    //div要素でタイルを作成
                    var tileDiv = L.DomUtil.create('div', '');
                    tileDiv.setAttribute("style", "border: solid 1px");
                    //タイル要素の中にzxyを表示するdiv要素を作成
                    var coordsDiv = L.DomUtil.create('div', '', tileDiv);
                    coordsDiv.setAttribute("style", "position:absolute; background-color:white; padding:5px; border:solid 1px; left:10px; top:10px; font-size:15px;");
                    coordsDiv.innerHTML = "z / x / y = " + coords.z + " / " + coords.x + " / " + coords.y;
                    return tileDiv;
                },
            });
            
            // 変数：地図オブジェクトと線オブジェクトは各関数で使うためグローバル変数にしておく
            var map;
            var pline;

            //
            // 画面表示時の初期処理
            // - 各チュートリアルの内容を本関数に集約したよ
            //
            function init() {
                // -------------------- 基本オブジェクトの作成 --------------------
                map = L.map('mapcontainer', {
                    zoomControl: false
                });

                // -------------------- 初期状態（地図の中心・ズームレベル） --------------------
                map.setView(COORD_BASE, 5);

                // -------------------- レイヤー（基本地図・切替地図・オーバレイ地図） --------------------
                //基本地図：標準地図タイル
                L.tileLayer(MAP_URL_STD, { attribution: ATTR_URL_STD }).addTo(map);
                //切替地図：標準地図タイル、淡色地図タイル、オープンストリートマップ）
                var gsi     = L.tileLayer(MAP_URL_STD , { attribution: ATTR_URL_STD });
                var gsipale = L.tileLayer(MAP_URL_PALE, { attribution: ATTR_URL_PALE });
                var osm     = L.tileLayer(MAP_URL_OPEN, { attribution: ATTR_URL_OPEN });
                var baseMaps = {
                    "地理院地図": gsi,
                    "淡色地図": gsipale,
                    "オープンストリートマップ": osm
                };
                //オーバレイ地図：色別標高図タイル、陰影起伏図タイル、グリッド線
                var gsirelief         = L.tileLayer(MAP_URL_RELIEF, { opacity: 0.7, maxNativeZoom: 15, attribution: ATTR_URL_STD　});
                var gsirehillshademap = L.tileLayer(MAP_URL_HILL  , { opacity: 0.5, maxNativeZoom: 16, attribution: ATTR_URL_STD });
                var zxyLayer          = new GridLayerClass();
                var overlayMaps = {
                    "色別標高図": gsirelief,
                    "陰影起伏図": gsirehillshademap,
                    "XYZ": zxyLayer
                };
                // 画面にセット
                L.control.layers(baseMaps, overlayMaps).addTo(map);
                gsi.addTo(map);  // ラジオボタンを選択状態にするために必要

                // -------------------- スケール（右下） --------------------
                L.control.scale({
                    maxWidth: 200,
                    position: 'bottomright',
                    imperial: false
                }).addTo(map);

                // -------------------- ズーム（左下、基本オブジェクト作成時にzoomControl:falseとする必要あり） --------------------
                L.control.zoom({
                    position: 'bottomleft'
                }).addTo(map);

                // -------------------- マーカー・ポップアップ・アイコン --------------------
                // アイコンオブジェクト
                var myIcon1 = L.divIcon({ className: 'icon1', iconAnchor: [13, 13] });  // アンカー：中央
                var myIcon2 = L.divIcon({ className: 'icon2', iconAnchor: [0, 0] });  // アンカー：左上
                // ポップアップあり、アイコンあり
                var popup1 = L.popup({ maxWidth: 550 }).setContent("埼玉大学です<br><img src='su.jpg' width='500' height='375'>");
                var popup2 = L.popup().setContent("桜区役所です");
                L.marker(COORD_SAITAMA_UNIV,  { icon: myIcon1 }).bindPopup(popup1).bindTooltip("埼玉大学").addTo(map);
                L.marker(COORD_SAKURA_KUYAKUSHO, { icon: myIcon2 }).bindPopup(popup2).bindTooltip("桜区役所").addTo(map);
                // ポップアップなし、アイコンなし
                //L.marker(COORD_SAITAMA_UNIV,  {title:"埼玉大学", draggable:true}).addTo(map);
                //L.marker(COORD_SAKURA_KUYAKUSHO, {title:"桜区役所"}).addTo(map);

                // 事前作成した複数のマーカーを表示
                var bound = L.latLngBounds(COORD_STOREs[0].pos, COORD_STOREs[0].pos);
                for (var num in COORD_STOREs) {
                    var mk    = COORD_STOREs[num];
                    var popup = L.popup().setContent(mk.name);
                    L.marker(mk.pos, { title: mk.name }).bindPopup(popup).addTo(map);
                    bound.extend(mk.pos);
                }
                // map.fitBounds(bound); // このマーカー群を初期表示位置にしたい場合はfitBoundsを使う（使わない場合はsetViewで設定した座標とズームが適用される）

                // -------------------- ベクタレイヤー（円・線・多角形、表示非表示） --------------------
                // 円（北浦和駅を中心に1000mごとの円を4つ作成する）
                var circleGroup = L.layerGroup();
                for (var i = 0; i < 4; i++) {
                    var r = i * 1000 + 1000;
                    circleGroup.addLayer(L.circle(COORD_KITAURAWA_STA, {
                            radius: r,
                            color: "#FF5555",
                            fill: false,
                            weight: 3
                        }).addTo(map)
                    );
                }
                // 円（地図の大きさに関係なく北浦和駅を円で囲む）
                var circleMarker = L.circleMarker(COORD_KITAURAWA_STA, {
                    radius: 20,
                    color: "#5555ff",
                    weight: 2,
                    fill: true,
                    fillColor: "#ffffff",
                    opacity: 0.5
                }).addTo(map);
                // 線（北浦和駅から埼玉大学までの道路）
                polyline = L.polyline(COORD_ROUTE, {
                    color: 'blue',
                    weight: 5,
                    bubblingMouseEvents: false  // イベントがmapオブジェクトに連鎖するのを防ぐ
                }).addTo(map);
                // 線（onLineClick関数で使うので空座標で一旦登録しておく）
                pline = L.polyline([], {
                    color: 'blue',
                    weight: 5,
                    bubblingMouseEvents: false  // イベントがmapオブジェクトに連鎖するのを防ぐ
                }).addTo(map)
                // 多角形（埼玉大学の敷地）
                var polygon = L.polygon(COORD_SAITAMA_UNIV_SITE, {
                    color: 'green',
                    weight: 2,
                    fill: true,
                    fillColor: 'green',
                    opacity: 0.5
                }).addTo(map);
                // 各ベクタレイヤをオブジェクトにまとめて、画面にセット
                var overlay = {
                    "polyline": polyline,
                    "Circle": circleGroup,
                    "CircleMarker": circleMarker,
                    "polygon": polygon
                }
                L.control.layers(null, overlay).addTo(map);

                // -------------------- 地図画面上にDiv要素の追加（左上、この例ではマウス移動した時の座標を表記） --------------------
                var latloninfo = L.control({ position: "topleft" });
                latloninfo.onAdd = function (map) {
                    //divを作成（最初は非表示、div上のとmousemoveイベントがmapに連鎖しないように設定）
                    this.ele = L.DomUtil.create('div', "infostyle");
                    this.ele.id = "latlondiv";
                    this.ele.style.visibility = "hidden";
                    this.ele.onmousemove = function (e) { e.stopPropagation() };
                    return this.ele;
                };
                latloninfo.addTo(map);

                // -------------------- イベント（click・mousemove） --------------------
                map.on('mousemove', onMapMousemove);
                map.on('click', onMapClick);
                pline.on('click', onLineClick);
            }

            //
            // 地図上のクリック
            // - クリック地点の座標にマーカーを追加
            // - マーカーがクリックされた場合は、onMarkerClick()を呼び出し
            // - クリックを繰り返すと線を引き続ける
            //
            function onMapClick(e) {
                L.marker(e.latlng).on('click', onMarkerClick).addTo(map);
                pline.addLatLng(e.latlng);
            }

            //
            // マーカーのクリック
            // - クリックされたマーカーを地図のレイヤから削除する
            //
            function onMarkerClick(e) {
                map.removeLayer(e.target);
            }

            //
            // 線のクリック
            // - クリックされた線を削除
            function onLineClick(e) {
                pline.setLatLngs([]);
            }

            //地図上を移動した際にdiv中に緯度経度を表示
            function onMapMousemove(e) {
                var box = document.getElementById("latlondiv");
                var html = "緯度:" + e.latlng.lat.toFixed(6) + "<br>" +
                           "経度:" + e.latlng.lng.toFixed(6);
                box.innerHTML = html;
                box.style.visibility = "visible";
            }

        </script>

    </head>

    <body onload="init()">
        <!-- 全画面表示するためにstyle設定 -->
        <div id="mapcontainer" style="position:absolute; top:0; left:0; right:0; bottom:0;"></div>
    </body>

</html>

```