Leafletで地図を作ろう - (気象庁震度観測点マップ)

1.気象庁震度観測点マップ

作品例として、気象庁震度観測点マップを作成します。この題材は複数の地図タイルの追加や任意のアイコンマーカーの追加、ポップアップの表示、スライドメニューの追加とカスタマイズなど 地図を拡張するための部品の実装要素の大部分を含んだ作品例としています。気象庁震度観測点一覧表は、気象庁ホームページより取得したものに 地図を作成するために必要な項目を追加したものをExcelで編集して、定義ファイルとしてExcelからソースコードを生成しました。

気象庁 : http://www.data.jma.go.jp/svd/eqev/data/kyoshin/jma-shindo.html

現在公開されている最新データは、令和2年10月29日現在のものですが、地図作成時点で取得したものは、令和元年11月14日現在のものを使用しています。 観測終了したものもあるため、差分更新が必要なため少し古いデータとなっています。時間的に余裕のあるときに更新を予定していますので、ご了承願います。 

2.ソースコードの説明

気象庁震度観測点マップのメインとなる(eqpointmap.html)コードです。各実装部品の定義ファイル類は外部ファイルとしています。 leaflet.css、all.css、eqpoint.css、leaflet.js、SlideMenu.js、jsframe.js、eqpoint.js、posdata.jsライブラリ等をヘッダー部分に指定しています。

SlideMenu.jsとSlideMenu.cssは、下記のGitHubからダウンロードできます。

取得先 : GitHubからダウンロード(スライドメニュー)

フローティング・ウィンドウ ライブラリ(JSFrame.js)はQiitaの下記のページにて紹介されていたものを 参考とさせて頂きました。IE以外のブラウザでは、ポップアップさせたウィンドウが地図の下側に隠れてしまうため IE専用として実装しました。(ブラウザの判定で対応)

取得先 : フローティング・ウィンドウ ライブラリ(JSFrame.js)

eqpoint.cssとeqpoint.js、posdata.jsは筆者が作成したものです。

DEMO

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta http-equiv="Content-Style-Type"  content="text/css">
<title>気象庁震度観測点マップ(令和元年11月14日現在)</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
  integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
  crossorigin=""/>
<link rel="stylesheet" type="text/css" media="screen" href="./css/eqpoint.css">
<link rel="stylesheet" href="./css/L.Control.SlideMenu.css" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"
   integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"
   integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
   crossorigin=""></script>
<script src="./jsscr/L.Control.SlideMenu.js"></script>
<script src="https://riversun.github.io/jsframe/jsframe.js"></script>
<script src='./jsscr/eqpoint.js'></script>
<script src='./jsscr/posdata.js'></script>
<script>
   function init(){
      //***** マップ中心座標の指定 *****
      var mpoint = [36.870832, 138.010254];
      var map = L.map('mapcontainer').setView(mpoint, 7);

      //***** 地図タイルを設定 *****
      var gsiattr = "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>地理院タイル</a>";
      var gsi = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', { attribution: gsiattr });
      var gsipale = L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', { attribution: gsiattr });
      var osm = L.tileLayer('http://tile.openstreetmap.jp/{z}/{x}/{y}.png',
          { attribution: "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors" });
      //***** オーバーレイ用のタイルレイヤ *****
      var gsirelief = L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
          { opacity: 0.5, maxNativeZoom: 15, attribution: gsiattr });
      var gsirehillshademap = L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png',
          { opacity: 0.4, maxNativeZoom: 16, attribution: gsiattr });
      var baseMaps = {
          "地理院地図": gsi,
          "淡色地図": gsipale,
          "オープンストリートマップ": osm
      };
      var overlayMaps = {
          "色別標高図": gsirelief,
          "陰影起伏図": gsirehillshademap
      };
      L.control.layers(baseMaps, overlayMaps).addTo(map);
      gsi.addTo(map);
      gsirelief.addTo(map);

      //***** Slide Menu Content html *****
      var header1  = '<span class="cstm-slidmenu-header" style=" background-color: #0033aa">防災関連情報</span>';
      var contents1  = '<table width=180 border=0 cellspacing=0 cellpadding=0 bgcolor="#303040" ' +
                       'onMouseOver="cellbgcolor(¥'' + "#3350a0" + '¥');" ' +
                       'onMouseOut="bgrecover(¥'' + "#303040" + '¥')">';
          for (var i = 0; i < SlideMenu_ma.length; i++) {
              var smenuDef = SlideMenu_ma[i];
              var text = smenuDef.title;
              contents1 += '<tr><td width=15 align="right"><img src=' + smenuDef.img + '></td>' +
                           '<td width=165 height=20 align="left" class="menustr" onClick="JumpURL(¥'' + smenuDef.url+ '¥')">' + text + '</td></tr>';
          }
          contents1 += '</table>';

      var header2  = '<span class="cstm-slidmenu-header" style="background-color: #dd3366">交通機関情報</span>';
      var contents2  = '<table width=180 border=0 cellspacing=0 cellpadding=0 bgcolor="#303040" ' +
                       'onMouseOver="cellbgcolor(¥'' + "#805080" + '¥');" ' +
                       'onMouseOut="bgrecover(¥'' + "#303040" + '¥')">';
          for (var i = 0; i < SlideMenu_mb.length; i++) {
              var smenuDef = SlideMenu_mb[i];
              var text = smenuDef.title;
              contents2 += '<tr><td width=15 align="right"><img src=' + smenuDef.img + '></td>' +
                           '<td width=165 height=20 align="left" class="menustr" onClick="JumpURL(¥'' + smenuDef.url+ '¥')">' + text + '</td></tr>';
          }
          contents2 += '</table>';

      var header3  = '<span class="cstm-slidmenu-header" style="background-color: #118833">その他関連情報</span>';
      var contents3  = '<table width=180 border=0 cellspacing=0 cellpadding=0 bgcolor="#303040" ' +
                       'onMouseOver="cellbgcolor(¥'' + "#009070" + '¥');" ' +
                       'onMouseOut="bgrecover(¥'' + "#303040" + '¥')">';
          for (var i = 0; i < SlideMenu_mc.length; i++) {
              var smenuDef = SlideMenu_mc[i];
              var text = smenuDef.title;
              contents3 += '<tr><td width=15 align="right"><img src=' + smenuDef.img + '></td>' +
                           '<td width=165 height=20 align="left" class="menustr" onClick="JumpURL(¥'' + smenuDef.url+ '¥')">' + text + '</td></tr>';
          }
          contents3 += '</table>';

        // ***** SlideMenu Option *****
        var options = {
            width:  '200px',
            height: '500px'
        }
        L.control.slideMenu(header1 + contents1 + header2 + contents2+ header3 + contents3 , options).addTo(map);

      //***** IEのみ利用可とする *****
      var userAgent = window.navigator.userAgent.toLowerCase();
      if (userAgent.indexOf('msie') !== -1 || userAgent.indexOf('trident') !== -1) {
         //***** ユーザーコントロールを追加(外部コンテンツ呼び出し用) *****
         var ourCustomControl = L.Control.extend({
              options: {
              position: 'topleft' 
         },
              onAdd: function (map) {
                 var container = L.DomUtil.create('div', 'leaflet-bar');
                 container.style.backgroundColor = '#ffffff';
                 container.style.width  = '30px';
                 container.style.height = '60px';
                 container.innerHTML = '<a href="javascript:popupwin02();">' +
                 '<img src="./image/ball0.png" width=24 height=24 title="地震発生履歴" align="absmiddle">' +
                 '<a href="javascript:popupwin03();"><img src="./image/graphic.png" width=24 height=24' +
                 ' title="観測点グラフ" align="absmiddle"></a>'
                 return container;
              },
         });
         map.addControl(new ourCustomControl());
      }

      //***** 凡例を右下に追加(1) *****
      var legend = L.control({position: 'bottomright'});
      legend.onAdd = function (map) {
          var div = L.DomUtil.create('div', 'info legend');
          var labels = [];
          for (var i = 0; i < colorDefs.length; i++) {
              var colorDef = colorDefs[i];
              var text = '';
              text = colorDef.title;
              labels.push('<span class="legend-item"><span class="legend-item-color" style="background:' + colorDef.color + '">' +
                          '</span> ' + text + '</span>');
          }
          div.innerHTML = labels.join('');
          return div;
      };
      legend.addTo(map);

      //***** 時計を右下に追加(2) *****
      var tokei = L.control({position: 'bottomright'});
           tokei.onAdd = function (map) {
           this._div = L.DomUtil.create('div', 'timeinfo');
           this.update();
           return this._div;
      };
      tokei.update = function () {
          this._div.innerHTML = '<span class="clock-info">' +
                                '<b><span id="c_time"></span></b></span>'
    };
     tokei.addTo(map);

    //***** 地震統計リンクボタンを右下に追加(3) *****
     var mymenu = L.control({position: 'bottomright'});
     mymenu.onAdd = function (map) {
          var div = L.DomUtil.create('div', 'menuinfo');
          var labels = [];
          for (var i = 0; i < menuDefs.length; i++) {
              var menuDef = menuDefs[i];
               if (i == 0) {
              labels.push('<span class="menu-info"><a href=' +menuDef.url + ' target="_blank">' + 
                          '<img src=' + menuDef.imgurl + ' border=0 width=185 height=34 align="bottom"></a></span><br>');
              }else{              
              labels.push('<span class="menu-info"><a href=' +menuDef.url + ' target="_blank">' + 
                          '<img src=' + menuDef.imgurl + ' border=0 width=185 height=34 align="top"></a></span>');
              }
          }
          div.innerHTML = labels.join('');
          return div;
     };
     mymenu.addTo(map);

     //***** IEのみ利用可とする *****
     var userAgent = window.navigator.userAgent.toLowerCase();
     if (userAgent.indexOf('msie') !== -1 || userAgent.indexOf('trident') !== -1) {
     //***** 地震発生回数を右下に追加(3) *****
     var eqcount = L.control({position: 'bottomright'});
         eqcount.onAdd = function (map) {
         this._div = L.DomUtil.create('div','countinfo');
         this.update();
         return this._div;
     };
         eqcount.update = function () {
         this._div.innerHTML = '<iframe src="./html/eqcount.html" width=184 class="ifstyle" height=60 scrolling=no></iframe>'
     };
     eqcount.addTo(map);
     }
     //***** タイトル画像を右下に追加(4) *****
     var img = L.control({position: 'bottomright'});
         img.onAdd = function (map) {
         this._div = L.DomUtil.create('div', 'imginfo');
         this.update();
         return this._div;
     };
     var linkurl = "https://twitter.com/tenkijp_jishin";
     img.update = function () {
     this._div.innerHTML = '<a href=' + linkurl + '  title="tenki.jp地震情報" target="_blank">' +
                           '<img src="./image/eqpoint.png" width=184 height=142></a>'
     };
     img.addTo(map);

     //***** スケールの表示 *****
     L.control.scale({imperial:false}).addTo(map);
 
     //***** マーカー全体が入るボックスを作る *****
     var bound = L.latLngBounds(markerList[0].pos, markerList[0].pos);

     //***** markerListの設定でマーカーを追加 *****
     for (var num in markerList) {
          var mk = markerList[num];
          var popup = L.popup().setContent(mk.name);

          //***** ツールチップ付加で地域ブロック毎にアイコン色分け表示 *****
          var mIcon = L.icon({
              iconUrl: mk.iconUrl,
              iconSize: mk.iconSize
          });
          L.marker(mk.pos,{ icon:mIcon}).bindPopup(popup).bindTooltip(mk.title).addTo(map);

          //***** マーカー全体が入るボックスを広げる *****
          bound.extend(mk.pos);
     }
   }
</script>
</head>
<body onload="init()">
  <div id="mapcontainer" style="position:absolute;top:0;left:0;right:0;bottom:0;"></div>
</body>
</html>

   //***** テキスト時計 *****
   function clock() {
       var twoDigit =function(num){
          var digit;
           if( num < 10 )
               { digit = "0" + num; }
           else { digit = num; }
           return digit;
   }
        var now = new Date();
        var hour = twoDigit(now.getHours());
        var minute = twoDigit(now.getMinutes());
        var second = twoDigit(now.getSeconds());
        document.getElementById("c_time").textContent = hour + ":" + minute + ":" + second;
   }
   setInterval(clock, 1000);

   //***** 直近の地震発生履歴 *****
   function popupwin02() {
        const jsFrame02 = new JSFrame();
        const frame02 = jsFrame02.create({
            name: 'gs-window',
            title: '地震発生履歴',
            left:50, top:8, width: 360, height: 490,
            resizable: false,
            appearanceName: 'redstone',
            style: {
                backgroundColor: 'rgba(200,200,200,0.2)',
                overflow: 'hidden'
            },
            html: '<iframe src="./html/eqresent.html"' +
                  ' class="ifstyle" width=360 height=490 scrolling=no></iframe>'
        });
        //指定したボタンを非表示にする (mini,max,zoom)
        frame02.hideFrameComponent('minimizeButton');
        frame02.hideFrameComponent('maximizeButton');
        frame02.hideFrameComponent('zoomButton');
        frame02.show();
        frame02.requestFocus();
    }
   //***** 観測点グラフ *****
   function popupwin03() {
        const jsFrame03 = new JSFrame();
        const frame03 = jsFrame03.create({
            name: 'gp-window',
            title: '震度観測点グラフ',
            left:50, top: 190, width: 720, height: 490,
            resizable: false,
            appearanceName: 'redstone',
            style: {
                backgroundColor: 'rgba(240,240,240,1.0)',
                overflow: 'hidden'
            },
            html: '<center><img src="./image/eqgpoint.png" border=0' +
                  ' align="absmiddle" width=660 height=456></center>'
        });
        //指定したボタンを非表示にする (mini,max,zoom)
        frame03.hideFrameComponent('minimizeButton');
        frame03.hideFrameComponent('maximizeButton');
        frame03.hideFrameComponent('zoomButton');
        frame03.show();
        frame03.requestFocus();
    }

    // ****** 背景色変更 ******
    var obj,evobj;
    function bgrecover(bg) { evobj.style.backgroundColor=bg; }
    function setColor(obj,col) { obj.style.backgroundColor = col; }
    function ResetColor(obj,col) { obj.style.backgroundColor = col; }

    // ****** 'table' , 'tr'タグ部分を避ける制御  *****
    function cellbgcolor(bg) {
        evobj=event.srcElement;
        if(evobj.tagName=="TABLE") {
           return
        }
        while(evobj.tagName!="TR") {
              evobj=evobj.parentElement;
        }
        evobj.style.backgroundColor=bg;
    }

    // ****** 別のHTMLに切替 (target="_blank") ******
    function JumpURL(URL) {
        window.open(URL,'_blank',
        "directories=no,location=yes,menubar=yes,resizable=yes,scrollbars=yes,status=yes,toolbar=yes");
    }

   //***** 地震発生履歴統計*****
   var menuDefs = [
      { url: './html/eqtoukei01.html',imgurl: './image/eqtoukei01.png' },
      { url: './html/eqtoukei02.html',imgurl: './image/eqtoukei02.png' }
   ];

   //***** 凡例:色定義 (地域別ブロック)*****
   var colorDefs = [
      { title: '北海道地方 (88)', color: '#98ecff' },
      { title: '東北地方 (99)', color: '#feddff' },
      { title: '関東・甲信地方 (113)', color: '#ab5dff' },
      { title: '北陸地方 (45)', color: '#ff53ff' },
      { title: '東海地方 (57)', color: '#ffc107' },
      { title: '近畿地方 (56)', color: '#76ca37' },
      { title: '中国地方 (45)', color: '#38a868' },
      { title: '四国地方 (36)', color: '#f03f00' },
      { title: '九州地方 (100)', color: '#3c81ff' },
      { title: '沖縄地方 (29)', color: '#7effeb' },
      { title: '合計: (668箇所)', color: '#ffffff' }
   ];  

  //***** SlideMenu-1 *****
  var SlideMenu_ma = [
      { title: '気象庁', url: 'http://www.jma.go.jp/jma/index.html',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '気象情報サービス (windy)', url: 'https://windy.com',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '気象情報サービス (earth)', url: 'https://earth.nullschool.net/jp/#current/wind/surface/level/orthographic=-230.56,32.17,399',
                img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '川の防災情報',url: 'https://www.river.go.jp/portal/#80',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '国土地理院', url: 'https://www.gsi.go.jp/',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '防災科研(NIED)', url: 'http://www.bosai.go.jp/',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '内閣府防災', url: 'https://twitter.com/CAO_BOUSAI',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: 'tenki.jp地震情報', url: 'https://twitter.com/tenkijp_jishin',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: '首相官邸', url: 'https://twitter.com/Kantei_Saigai',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' },
      { title: 'NHKニュース', url: 'https://twitter.com/nhk_news',img: '"./image/ball8.png" width=8 height=8 align="absmiddle"' }
   ];
  //***** SlideMenu-2 *****
  var SlideMenu_mb = [
      { title: '日本道路交通情報センター', url: 'http://www.jartic.or.jp/',img: '"./image/ball3.png" width=8 height=8 align="absmiddle"' },
      { title: '道路防災情報',url: 'http://www.mlit.go.jp/road/bosai/bosai.html',img: '"./image/ball3.png" width=8 height=8 align="absmiddle"' },
      { title: 'JR東日本列車運行情報', url: 'https://traininfo.jreast.co.jp/train_info/shinkansen.aspx',img: '"./image/ball3.png" width=8 height=8 align="absmiddle"' },
      { title: '東京メトロ運行情報', url: 'https://www.tokyometro.jp/index.html#UnkouLinesList',img: '"./image/ball3.png" width=8 height=8 align="absmiddle"' },
      { title: '羽田空港フライト情報', url: 'https://www.tokyo-airport-bldg.co.jp/flight/',img: '"./image/ball3.png" width=8 height=8 align="absmiddle"' },
      { title: '成田空港フライト情報', url: 'http://www.narita-airport.or.jp/',img: '"./image/ball3.png" width=8 height=8 align="absmiddle"' }
   ];
  //***** SlideMenu-3 *****
  var SlideMenu_mc = [
      { title: 'ハザードマップポータルサイト', url: 'https://disaportal.gsi.go.jp/',img: '"./image/ball5.png" width=8 height=8 align="absmiddle"' },
      { title: '統合災害情報システム(DiMAPS)',url: 'http://www.mlit.go.jp/saigai/dimaps/index.html',img: '"./image/ball5.png" width=8 height=8 align="absmiddle"' },
      { title: '地震調査研究推進本部', url: 'https://www.jishin.go.jp/',img: '"./image/ball5.png" width=8 height=8 align="absmiddle"' },
      { title: '最新津波情報', url: 'http://www.jma.go.jp/jp/tsunami/',img: '"./image/ball5.png" width=8 height=8 align="absmiddle"' }
   ];

a          { font-size: 11px;text-decoration:none; color:#003399;font-family:Meiryo UI; }
a:link     { color:#003399; }
a:hover    { color:#003399; }
a:visited  { color:#003399; }
body       { font-size: 11px;font-family:Meiryo; }
.info {
            padding: 10px 10px; 
            font-color: #333333;
            background: white;
            border-radius: 3px;
            opacity: 0.9;
} 
.legend-item {
            font-size: 11px;
            display: flex;
            width: 165px;
            align-items: center;
            color: #555;
}
.legend-item-color {
            display: block;
            width: 15px; 
            height: 15px;
            margin-right: 5px; 
}
.clock-info {
            display: block;
            width: 166px;
            height: 48px;
            padding-left: 20px;
            margin-top: 1px;
            margin-bottom: 1px;
            background: #ff9907;
}
.menu-info {
            width: 185px;
            height: 34px;
            padding: 0px;
            margin-top: 0px;
            margin-bottom: 0px;
}
.count-info {
            display: block;
            width: 185px;
            height: 60px;
            margin-top: 1px;
            margin-bottom: 1px;
            background: #228800;
}
.ifstyle {
            border-left: 0px solid #ffffff;
            border-right: 0px solid #ffffff;
            border-top: 0px solid #ffffff;
            border-bottom: 0px solid #ffffff;
}
b        {  font-family : Meiryo UI;
            font-size:30px;
            font-weight:bold;
            color : #ffffff;
}
.leaflet-menu {
          background-color: #fafafa;
          opacity: 1.0;
}
.cstm-slidmenu-header {
          display: block;
          margin: 0;
          font-size: 14px;
          font-weight: bold;
          width: 180px;
          color: #ffffff;
          text-align : center;
}
.menustr {
          font-size: 11px;
          font-family: Meiryo UI;
          padding-left: 6px;
          color: #ffffff;
          cursor: pointer;
}

2-1.地図の起動処理

ブラウザが起動すると、<body>タグに.記述してあるonload関数でinit()関数を実行して地図を表示します。

<body onload="init()">
  <div id="mapcontainer" style="position:absolute;top:0;left:0;right:0;bottom:0;"></div>
</body>

2-2.マップ中心座標設定

マップの中心座標設定と地図の拡大倍率を設定して、変数mapにセットします。

<script>
   function init(){
      //***** マップ中心座標の指定 *****
      var mpoint = [36.870832, 138.010254];
      var map = L.map('mapcontainer').setView(mpoint, 7);

2-3.地図タイル設定

複数地図タイルの切り替えやオーバーレイで組み合わせて利用できるように設定します。無料で利用できる地図タイルを 設定して下さい。国土地理院の標準地図や淡色地図、オープンストリートマップなどが無料で利用可能です。 地図画面右上に地図タイル選択コントールの配置とデフォルトの地図タイルとオーバーレイ図をセットします。 デフォルトセット値は、地理院地図と色別標高図をセットしています。尚、色別標高図は拡大倍率に影響します。

      //***** 地図タイルを設定 *****
      var gsiattr = "<a href='http://portal.cyberjapan.jp/help/termsofuse.html' target='_blank'>地理院タイル</a>";
      var gsi = L.tileLayer('https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png', { attribution: gsiattr });
      var gsipale = L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png', { attribution: gsiattr });
      var osm = L.tileLayer('http://tile.openstreetmap.jp/{z}/{x}/{y}.png',
          { attribution: "<a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors" });
      //***** オーバーレイ用のタイルレイヤ *****
      var gsirelief = L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
          { opacity: 0.5, maxNativeZoom: 15, attribution: gsiattr });
      var gsirehillshademap = L.tileLayer('http://cyberjapandata.gsi.go.jp/xyz/hillshademap/{z}/{x}/{y}.png',
          { opacity: 0.4, maxNativeZoom: 16, attribution: gsiattr });
      var baseMaps = {
          "地理院地図": gsi,
          "淡色地図": gsipale,
          "オープンストリートマップ": osm
      };
      var overlayMaps = {
          "色別標高図": gsirelief,
          "陰影起伏図": gsirehillshademap
      };
      L.control.layers(baseMaps, overlayMaps).addTo(map);
      gsi.addTo(map);
      gsirelief.addTo(map);

2-4.スライドメニューコントロールの追加

スライドメニューコントロールプラグインを追加します。地図画面上の左右どちら側でも実装できますが、今回は左側に実装して見ました。 スライドメニューが追加されるとコントロールアイコンが表示されます。コントロールアイコンをクリックするとスライドメニューが開きます。 スライドメニューの実装においてHTMLタグを埋め込めるので自分の好みのデザインにカスタマイズすることが可能です。 メニューは、項目のタイトル部分と項目名部分で構成して、3つに分けて作成しました。tableタグとJavaScriptを利用してメニュー選択時 にメニュー項目の色を変えるようにしています。メニュー項目名とメニュー選択時のジャンプ先URL、メニュー名先頭に配置する画像ファイル名を eqpoint.jsという外部ファイル内に定義してある配列から読み込み展開します。最後にそれを合成して地図に反映させます。メニューレイヤーの 縦横サイズは、optionsで設定します。

      //***** Slide Menu Content html *****
      var header1  = '<span class="cstm-slidmenu-header" style=" background-color: #0033aa">防災関連情報</span>';
      var contents1  = '<table width=180 border=0 cellspacing=0 cellpadding=0 bgcolor="#303040" ' +
                       'onMouseOver="cellbgcolor(¥'' + "#3350a0" + '¥');" ' +
                       'onMouseOut="bgrecover(¥'' + "#303040" + '¥')">';
          for (var i = 0; i < SlideMenu_ma.length; i++) {
              var smenuDef = SlideMenu_ma[i];
              var text = smenuDef.title;
              contents1 += '<tr><td width=15 align="right"><img src=' + smenuDef.img + '></td>' +
                           '<td width=165 height=20 align="left" class="menustr" onClick="JumpURL(¥'' + smenuDef.url+ '¥')">' + text + '</td></tr>';
          }
          contents1 += '</table>';

      var header2  = '<span class="cstm-slidmenu-header" style="background-color: #dd3366">交通機関情報</span>';
      var contents2  = '<table width=180 border=0 cellspacing=0 cellpadding=0 bgcolor="#303040" ' +
                       'onMouseOver="cellbgcolor(¥'' + "#805080" + '¥');" ' +
                       'onMouseOut="bgrecover(¥'' + "#303040" + '¥')">';
          for (var i = 0; i < SlideMenu_mb.length; i++) {
              var smenuDef = SlideMenu_mb[i];
              var text = smenuDef.title;
              contents2 += '<tr><td width=15 align="right"><img src=' + smenuDef.img + '></td>' +
                           '<td width=165 height=20 align="left" class="menustr" onClick="JumpURL(¥'' + smenuDef.url+ '¥')">' + text + '</td></tr>';
          }
          contents2 += '</table>';

      var header3  = '<span class="cstm-slidmenu-header" style="background-color: #118833">その他関連情報</span>';
      var contents3  = '<table width=180 border=0 cellspacing=0 cellpadding=0 bgcolor="#303040" ' +
                       'onMouseOver="cellbgcolor(¥'' + "#009070" + '¥');" ' +
                       'onMouseOut="bgrecover(¥'' + "#303040" + '¥')">';
          for (var i = 0; i < SlideMenu_mc.length; i++) {
              var smenuDef = SlideMenu_mc[i];
              var text = smenuDef.title;
              contents3 += '<tr><td width=15 align="right"><img src=' + smenuDef.img + '></td>' +
                           '<td width=165 height=20 align="left" class="menustr" onClick="JumpURL(¥'' + smenuDef.url+ '¥')">' + text + '</td></tr>';
          }
          contents3 += '</table>';

        // ***** SlideMenu Option *****
        var options = {
            width:  '200px',
            height: '500px'
        }
        L.control.slideMenu(header1 + contents1 + header2 + contents2+ header3 + contents3 , options).addTo(map);

2-5.フローティング・ウィンドウの追加

GoogleChromやMicrosoftEdgeでは意図した動作とならないため、実装はしていますがお勧めしません。ここでは説明を割愛します。 フローティング・ウィンドウを利用しないのであれば、htmlやjavascriptを利用したコンテンツなどは利用できます。 container.innerHTML = 'XXXXXXXXX'部分を書き換えればクリックイベントに反応する普通のユーザーコントロールとして利用できます。 配置位置は、optionsで設定し、コントロールのサイズは、container.style.widthとcontainer.style.heighで指定します。 1つのサイズは、30px×30pxとして設定するアインサイズは、24px×24pxとすると良いでしょう。コントロールを2つとすれば縦サイズは 60pxとなります。container.innerHTML部分の記述例はeapoint.htmlの該当部分を参考として下さい。

         //***** ユーザーコントロールを追加(外部コンテンツ呼び出し用) *****
         var ourCustomControl = L.Control.extend({
              options: {
              position: 'topleft' 
         },
              onAdd: function (map) {
                 var container = L.DomUtil.create('div', 'leaflet-bar');
                 container.style.backgroundColor = '#ffffff';
                 container.style.width  = '30px';
                 container.style.height = '60px';
                 container.innerHTML = 'xxxxxxxxxxx' ← ここの部分にリンク(起動)させるコンテンツを記入する。
                 return container;
              },
         });
         map.addControl(new ourCustomControl());

2-6.凡例などの追加

地図画面上に凡例を追加します。作品例では全国を10ブロックとして地域ブロック名と色定義したものを作成しています。 これは、地域ブロック毎に色分け配置したアイコンマーカーの色を表します。eqpoint.js内で定義した配列から展開します。 他ののコントロールも同様にして作成しますが、注意事項としてコードの記述順とは逆に配置されます。上から順に記述すれば 画面上では下から順に記述した順番で配置されます。簡単な時計や画像ボタンの貼付けができます。 貼り付けたコントロールに<iframe>を利用してhtmlなどで作成した外部ファイルを呼び出すとIE以外のブラウザでは地図画面の下に 隠れてしまいますので、利用は避けた方が良いでしょう。色々な場面でIEの利用は難しい状況ですね。ただ、企業などでは業務 システムにまだ多くIEベースで作成されたwebシステムが利用されているので当分は残りそうですね。

      //***** 凡例を右下に追加(1) *****
      var legend = L.control({position: 'bottomright'});
      legend.onAdd = function (map) {
          var div = L.DomUtil.create('div', 'info legend');
          var labels = [];
          for (var i = 0; i < colorDefs.length; i++) {
              var colorDef = colorDefs[i];
              var text = '';
              text = colorDef.title;
              labels.push('<span class="legend-item"><span class="legend-item-color" style="background:' + colorDef.color + '">' +
                          '</span> ' + text + '</span>');
          }
          div.innerHTML = labels.join('');
          return div;
      };
      legend.addTo(map);

      //***** 時計を右下に追加(2) *****
      var tokei = L.control({position: 'bottomright'});
           tokei.onAdd = function (map) {
           this._div = L.DomUtil.create('div', 'timeinfo');
           this.update();
           return this._div;
      };
      tokei.update = function () {
          this._div.innerHTML = '<span class="clock-info">' +
                                '<b><span id="c_time"></span></b></span>'
    };
     tokei.addTo(map);

    //***** 地震統計リンクボタンを右下に追加(3) *****
     var mymenu = L.control({position: 'bottomright'});
     mymenu.onAdd = function (map) {
          var div = L.DomUtil.create('div', 'menuinfo');
          var labels = [];
          for (var i = 0; i < menuDefs.length; i++) {
              var menuDef = menuDefs[i];
               if (i == 0) {
              labels.push('<span class="menu-info"><a href=' +menuDef.url + ' target="_blank">' + 
                          '<img src=' + menuDef.imgurl + ' border=0 width=185 height=34 align="bottom"></a></span><br>');
              }else{              
              labels.push('<span class="menu-info"><a href=' +menuDef.url + ' target="_blank">' + 
                          '<img src=' + menuDef.imgurl + ' border=0 width=185 height=34 align="top"></a></span>');
              }
          }
          div.innerHTML = labels.join('');
          return div;
     };
     mymenu.addTo(map);

     //***** IEのみ利用可とする *****
     var userAgent = window.navigator.userAgent.toLowerCase();
     if (userAgent.indexOf('msie') !== -1 || userAgent.indexOf('trident') !== -1) {
     //***** 地震発生回数を右下に追加(3) *****
     var eqcount = L.control({position: 'bottomright'});
         eqcount.onAdd = function (map) {
         this._div = L.DomUtil.create('div','countinfo');
         this.update();
         return this._div;
     };
         eqcount.update = function () {
         this._div.innerHTML = '<iframe src="./html/eqcount.html" width=184 class="ifstyle" height=60 scrolling=no></iframe>'
     };
     eqcount.addTo(map);
     }
     //***** タイトル画像を右下に追加(4) *****
     var img = L.control({position: 'bottomright'});
         img.onAdd = function (map) {
         this._div = L.DomUtil.create('div', 'imginfo');
         this.update();
         return this._div;
     };
     var linkurl = "https://twitter.com/tenkijp_jishin";
     img.update = function () {
     this._div.innerHTML = '<a href=' + linkurl + '  title="tenki.jp地震情報" target="_blank">' +
                           '<img src="./image/eqpoint.png" width=184 height=142></a>'
     };
     img.addTo(map);

2-7.地図スケールの追加

地図にスケール表示を追加するのは非常に簡単です。1行記述するだけです。

  
     //***** スケールの表示 *****
     L.control.scale({imperial:false}).addTo(map);

2-8.アイコンマーカーの配置とポップアップ

大量のアイコンマーカーの配置とポップアップ(吹き出し)を指定した座標(緯度、経度)の位置に展開します。 種類の違う複数の任意アイコン画像のマーカーを指定した位置に配置するために外部ファイルとしてposdata.jsとして作成します。 定義ファイルは、markerList配列としてpos(マーカー配置位置:緯度,経度),iconUrl(アイコン画像のファイル名),iconSize(アイコンサイズ),title(ツールチップ表示名), name(ポップアップ内の画像、観測点名、地域名、観測点の所在地)を定義します。 これをforループで読み込みL.marker(mk.pos,{ icon:mIcon}).bindPopup(popup).bindTooltip(mk.title).addTo(map)で展開、反映させます。 posdata.jsとして外部ファイル化したのでマーカーとポップアップ部分の処理が簡潔に記述することができました。

  
     //***** マーカー全体が入るボックスを作る *****
     var bound = L.latLngBounds(markerList[0].pos, markerList[0].pos);

     //***** markerListの設定でマーカーを追加 *****
     for (var num in markerList) {
          var mk = markerList[num];
          var popup = L.popup().setContent(mk.name);

          //***** ツールチップ付加で地域ブロック毎にアイコン色分け表示 *****
          var mIcon = L.icon({
              iconUrl: mk.iconUrl,
              iconSize: mk.iconSize
          });
          L.marker(mk.pos,{ icon:mIcon}).bindPopup(popup).bindTooltip(mk.title).addTo(map);

          //***** マーカー全体が入るボックスを広げる *****
          bound.extend(mk.pos);
     }


※リスト表示にsyntaxhighlighterを利用していますが、スクリプト中に<br>タグを含んでいる 箇所が何箇所かあり、posdata.jsコード表示が正しく表示されないため掲載していません。コードは一部分のみのサンプルですが、下記から確認して下さい。

posdata.jsファイル確認


3.アイコンマーカー定義ファイルの作り方

マーカーを設置する箇所が多いと配列化した定義ファイルを作成するのにも非常に手間がかかり大変な作業となります。 今までは、このようなコードファイルを作成する場合は、テキストエディタで1行分作って行をコピーしてデータの変更部分 をExcelから一つずつコピーして作成していました。ただ、ひたすらデータの数分コピー&ペーストする作業の繰り返しです。 何とか効率良く作成できないものかと試行錯誤した結果、データの整理はExcelでやっているのでExcelから定義ファイルを作成することとしました。 その作成手順を紹介します。先に説明したposdata.jsはこの方法で作成して効率化が図れました。

3-1.原単位のデータファイルシートの準備

元となる整理したデータシートファイルを準備します。

3-2.コードとデータ部分の分離シートの作成

コード部分とデータ挿入部分のシートを作成します。まず最初に先頭行の各列にコードを挿入します。 次にデータ挿入列にデータシートより列単位にデータをコピーして貼り付けます。コード部分の各列毎に最終行までドラッグしてコピーしていきます。 アイコンファイル名など部分的に変更する箇所があれば手作業で変更します。

3-3.シートデータの範囲選択と貼付け

シートデータが完成したら、データ部分を最終行まで選択してコピーします。このファイル例では、C3セルからT670セルまでを選択してコピーしています。

3-4.タブをnullへの置換編集

Excelからコピーされたデータをテキストエディタ(メモ帳でも可)へ貼付け、タブをnuuへ置換で一括変換します。

3-5.置換編集後のデータ

Excelから貼付けされ置換編集されたデータが完成しますが、コードしては不完全な状態なので補完します。先頭行と最終行にコード補完するだけです。

3-6.先頭部と最終行に不足分コードの追記

先頭部に

var gmapstr = "https://maps.google.co.jp/maps?q=";

var markerList = [

と最終行に

];

をそれぞれコード補完し完成させます。これをjsファイルとして名前を付けて保存します。


4.ソースコードについて

掲載しているソースコードのライセンスは、CC0 (クレジット表示不要、改変可、商用可) とします。自由に利用して頂いてかまいません。 作品で利用しているアイコンマーカー画像ファイル等は提供していませんので、お手数ですが各自で準備して下さい。尚、ソースコードは予告なく修正を加えて 更新することがあります。予めご了承願います。また、ブラウザのソースコード表示などで表示して確認やコピー、URLから直接ダウンロードするなども自由に行ってもかまいません。 全て自己責任でお願いします。

■関連記事
・Leafletで地図を作ろう - (イントロダクション)
・Leafletで地図を作ろう - (都道府県別人口統計マップ)
・Leafletで地図を作ろう - (東海道五十三次ルートマップ)
・Leafletで地図を作ろう - (富嶽三十六景浮世絵マップ)
・Leafletで地図を作ろう - (四国遍路巡礼マップ)

コメント

このブログの人気の投稿

Excelアドインで日本語形態素解析

階層構造JSONファイルの作成

HSPでコマンドプロンプトを制御する

TOP