タイル地図の表示WMTSの表示KML,GPX,GeoJSONの読み込み円を描くポリゴンを描くポリラインを描くマーカーを配置するポップアップクリック位置の座標中心位置の座標地名検索地図切り替え地質図凡例表示地質図凡例とポップアップマーカー標高タイルで標高値を得る地理院地図vectorの表示<注>地図タイルについて

 OpenLayers Ver.4-5 TIPS

 地学では地図類を多用します。この「かだいおうち Advanced Course」は最初、Google Maps APIを使用して地図類を表示していました。途中、Google社のポリシー変更があったため、FOSS(Free Open Source Software)のOpenLayersに変更しました。当時はver. 5を開発中でしたので(現在はver. 6開発中)、安定版であるver. 4.6.5を使用することにしました。以下、ver. 4のTIPSをいくつかご紹介します。OpenLayersは、ver.2からver. 3へはドラスティックに変わりましたが、ver.4以降は基本的に下位互換のようですから(少なくともver.5では動きます)、参考にはなるかと思います。Ver.6のTIPSは別ページとします。
蛇足: 本文のプログラムをタイプしたり、コピペする必要はありません。「サンプルプログラムのURL」を右クリックして「名前を付けてリンクを保存」を選び、適当なところに保存してください。メモ帳など普通のエディタで開けば一目瞭然です。ダブルクリックすれば、サーバ上になくても動きます。ただし、kmlとGPXはhtml本体をサーバに置かないと読み込まないようです。

 タイル地図の表示

 地図類のネット配信は現在タイル地図配信(Tile Map Service)が主流です<注>。国土地理院の地理院地図や産総研のシームレス地質図もそうです。以下のようにすれば表示されます。先ずはhtml本体。
 なお、OpenLayersでは位置情報は経度・緯度の順に記述するルールになっています(Google Mapは緯度・経度の順です)。数学ではXYの順に並べるからです。蛇足ながら、タイルを呼び出すURLの末尾、普通は{z}/{x}/{y}.pngの順なのですが、産総研だけ{z}/{y}/{x}.pngの順になっています。ご注意を。透明度は opacity で加減します(0.0~1.0)。
サンプルデータを全て表示した例(ただし、緯度経度は出ません)
(右上両矢印クリックで全画面表示になります)
地図表示だけのサンプルプログラムのURL
<!DOCTYPE html>
<html lang="ja">
  <head>
<title>OpenLayers 4 サンプル</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <link rel="stylesheet" href="https://openlayers.org/en/v4.6.5/css/ol.css" type="text/css">
    <!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
    <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
    <script src="https://openlayers.org/en/v4.6.5/build/ol.js"></script>
  </head>
<body>
   <div id="map" style="width: 500px; height: 800px;"></div>
<script>
// 地理院地図
      var gsi_layer = new ol.layer.Tile({
      source: new ol.source.XYZ({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院地図</a>"
          })
         ],
        url: "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
        projection: "EPSG:3857"
       })
      });
// 産総研地質図
      var gsj_layer = new ol.layer.Tile({
      opacity: 0.7,
      source: new ol.source.XYZ({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://gbank.gsj.jp/seamless/v2/api/1.2/legend.html' target='_blank'>産総研</a>"
         })
        ],
        url: "https://gbank.gsj.jp/seamless/v2/api/1.2/tiles/{z}/{y}/{x}.png",
        projection: "EPSG:3857"
       })
      });
      var map = new ol.Map({
        layers: [gsi_layer,gsj_layer],
        target: document.getElementById('map'),
        controls: ol.control.defaults().extend([new ol.control.FullScreen(), new ol.control.ScaleLine()]),
        view: new ol.View({
          center: ol.proj.fromLonLat([130.55, 31.65]),
          zoom: 11
        })
      });
   </script>
</body>
</html>

 WMTSの表示

 防災科研の地すべり地形分布図のようにWMTS配信(Web Map Tile Service)のものもあります。少し違います。
防災科研地すべり地形分布図
サンプルプログラムのURL
// 地すべり地形分布図
      var wmsSource = new ol.source.TileWMS({
        attributions: [
          new ol.Attribution({
            html: "<a href='http://www.j-shis.bosai.go.jp/JSHIS2/IMAGE/etc/landslide_v3_jp.png' target='_blank'>防災科研</a>"
         })
        ],
        url: 'http://www.j-shis.bosai.go.jp/map/wms/landslide',
        params: {'LANG': 'en', 'VERSION': '1.3.0', 'REQUEST': 'GetMap', 'WIDTH': 512,'HEIGHT': 512, 'LAYERS': 'L-V3-ALL', 'STYLES': 'default', 'TRANSPARENT': true, 'FORMAT': 'image/png','TILED': true},
        projection: "EPSG:4326"
      });
      var nied_layer = new ol.layer.Tile({
        opacity: 0.7,
        source: wmsSource,
      });
 var map の前に記述し、上記 var map = new ol.Map({ の次の行に layers: [gsi_layer,gsj_layer,nied_layer], と nied_layer を書き加えます。要するに、この layers の中に必要なレイヤーを“,”で列挙すれば良いのです(名称は自由)。後ろに記載するほうが上位のレイヤーになります。

 KML, GPX, GeoJSONの読み込み

 タイルでなくても、KMLやGeoJSONを読み込み表示することもできます。また、地学関係者はGPSを持ち歩く人が多いようですが、そのデータGPXファイルも直接読み込めます。KMLは自身でスタイル情報を持っていますから不要ですが、他は style を指定する必要があります。もちろん、format の指定が一番肝心です。あとは、上記 var map の layers の中に kml_layer,gpx_layer, json_layer を登録すればOKです。
1920年の鹿児島市域(GeoJSON)と甲突川水系(kml)
ある日の鹿児島県地学会巡検ルート(gpx)
サンプルプログラムのURL
// KMLファイルの読み込み
      var kml_layer = new ol.layer.Vector({
        opacity: 0.8,
        source: new ol.source.Vector({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://nlftp.mlit.go.jp/index.html' target='_blank'>国土数値情報</a>"
         })
        ],
        url: 'https://www.sci.kagoshima-u.ac.jp/oyo/advanced/webmap/kotsuki.kml',   // 読み込むkmlファイル名
          format: new ol.format.KML()
        })
      });
// GPXファイルの読み込み
      var style0 = new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#00ff00',
          width: 3
        })
      });
     var gpx_layer = new ol.layer.Vector({
       source: new ol.source.Vector({
        attributions: [
          new ol.Attribution({
            html: "<a href='http://www5.synapse.ne.jp/k_chigaku/' target='_blank'>鹿児島県地学会</a>"
         })
        ],
         url: 'https://www.sci.kagoshima-u.ac.jp/oyo/advanced/webmap/chigakkai.gpx',   // 読み込むgpxファイル名         format: new ol.format.GPX()
       }),
    style: style0
    });
// GeoJSONファイルの読み込み
      var style1 = new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: '#ff00ff',
          width: 3
        })
      });
     var json_layer = new ol.layer.Vector({
       source: new ol.source.Vector({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://geoshape.ex.nii.ac.jp/ city/resource/46201A1968.html' target='_blank'>NII-CODH</a>"
         })
        ],
         url: 'https://geoshape.ex.nii.ac.jp/city/ geojson/19200101/46/46201A1968.geojson',   // 読み込むgeojsonファイル名
         format: new ol.format.GeoJSON()
       }),
    style: style1
    });

 円を描く

 var map の前に記述し、上記 layers の中に circle1_layer を登録します。
桜島南岳から半径10km圏
サンプルプログラムのURL
// 円を描く
	var circle1 = new ol.Feature({
	        geometry: new ol.geom.Circle(ol.proj.fromLonLat([130.65823388285312, 31.57972292319624]),10000),   // 中心経度,緯度,半径(メートル)
                name: '<small>桜島南岳</small>'
	});
        var style1 = new ol.style.Style({
          fill: new ol.style.Fill({
              color: 'rgba(255,0,0,0.2)',
          }),
          stroke: new ol.style.Stroke({
             color: 'rgb(255,0,0)',
             width: 1.0
          })
        });
      var vectorSource = new ol.source.Vector({
        features: [circle1]
      });
      var circle1_layer = new ol.layer.Vector({
        source: vectorSource,
        style: style1
      });

 ポリゴンを描く

 var map の前に記述し、上記 layers の中に polygon2_layer を登録します。
鹿児島大学郡元キャンパス
サンプルプログラムのURL
// ポリゴンを描く
var coordinates = [[
   [ 130.54672908969178, 31.573269840331626 ],
   [ 130.5463428515973, 31.57237405131277 ],
   [ 130.54657888598638, 31.56833375307527 ],
   [ 130.54552746005743, 31.56553652097084 ],
   [ 130.5442185420595, 31.56553652097084 ],
   [ 130.54381084628508, 31.56568278355216 ],
   [ 130.54237318225267, 31.56736478674677 ],
   [ 130.54104280658382, 31.571149183009176 ],
   [ 130.5416650790764, 31.571332000195945 ],
   [ 130.5426950473344, 31.573672028520605 ],
   [ 130.54301691241616, 31.57372687223007 ],
   [ 130.5437035579215, 31.57398280911379 ],
   [ 130.5442185420595, 31.574110777292923 ],
   [ 130.54460478015403, 31.5740559338093 ],
   [ 130.54672908969178, 31.573269840331626 ]
]]
var polygon = new ol.geom.Polygon([]);
polygon.setCoordinates(coordinates); 
var vectorFeature = new ol.Feature(
  polygon.transform('EPSG:4326', 'EPSG:3857')
);
        var style2 = new ol.style.Style({
          stroke: new ol.style.Stroke({
             color: 'rgb(0,0,255)',
             width: 2.0
          }),
          fill: new ol.style.Fill({
              color: 'rgba(0,0,255,0.2)',
          })
        });
    var vectorSource  = new ol.source.Vector({
        features: [vectorFeature]
    });
// ポリゴンの vector layer の作成
    var polygon2_layer = new ol.layer.Vector({
        source: vectorSource,
        style: style2
    });

 ポリラインを描く

 var map の前に記述し、上記 layers の中に polyline3_layer を登録します。点が多い場合にはKMLの活用が便利かも知れません。
薩摩街道出水筋(西目筋)
サンプルプログラムのURL
<別法>
 ポリラインは要するに折れ線の集合ですから、繰り返し処理など使わなくても、折れ線の座標を列挙しても描くことができます。短い線の場合には、このほうが直截的で分かりやすいかも知れません。

// ポリラインを描く
    var lineStrArray = new Array(
        [[130.54565,31.5888],[130.54479,31.58855]],
        [[130.54479,31.58855],[130.53917,31.58855]],
   ・
   ・
        [[130.52269,31.59277],[130.52264,31.59338]],
        [[130.52264,31.59338],[130.52257,31.59351]],
   ・
   ・
);
    var lineStrings = new ol.geom.MultiLineString([]);
    lineStrings.setCoordinates(lineStrArray);
    var vectorFeature = new ol.Feature(
   ・
   ・
  以下、左に同じ
// ポリラインを描く
    var lineStrArray = new Array();
    var coordArray = new Array();
coordArray.push([130.54565,31.5888]);
coordArray.push([130.54479,31.58855]);
coordArray.push([130.53917,31.58855]);
coordArray.push([130.53634,31.58859]);
coordArray.push([130.53456,31.58858]);
coordArray.push([130.53234,31.5882]);
coordArray.push([130.5315,31.58825]);
coordArray.push([130.53081,31.58814]);
coordArray.push([130.53049,31.58812]);
coordArray.push([130.53011,31.5884]);
coordArray.push([130.52946,31.58852]);
coordArray.push([130.52881,31.58886]);
coordArray.push([130.52816,31.58904]);
coordArray.push([130.52757,31.58932]);
coordArray.push([130.52712,31.58935]);
coordArray.push([130.52679,31.58945]);
coordArray.push([130.52635,31.58998]);
coordArray.push([130.52624,31.59007]);
coordArray.push([130.52551,31.59039]);
coordArray.push([130.52466,31.59112]);
coordArray.push([130.52421,31.59153]);
coordArray.push([130.52384,31.59216]);
coordArray.push([130.52295,31.59261]);
coordArray.push([130.52269,31.59277]);
coordArray.push([130.52264,31.59338]);
coordArray.push([130.52257,31.59351]);
   ・
   ・
    for (i=0; i<(coordArray.length-1); i++) {
        lineStrArray.push([coordArray[i], coordArray[i+1]]);
    }
    var lineStrings = new ol.geom.MultiLineString([]);
    lineStrings.setCoordinates(lineStrArray);
    var vectorFeature = new ol.Feature(
        lineStrings.transform('EPSG:4326', 'EPSG:3857')
    );
    var vectorSource  = new ol.source.Vector({
        features: [vectorFeature]
    });
// 経路用の vector layer の作成
    var polyline3_layer = new ol.layer.Vector({
        source: vectorSource,
        style: new ol.style.Style({
            stroke: new ol.style.Stroke({ color: '#ff0000', width: 4 })
        })
    });

 マーカーを配置する

 以下を末尾 </script> の前に追加します。
マーカー表示(名称はポップアップしません)
:市役所,:中央駅,:県庁,:錫山鉱山
サンプルプログラムのURL
 
<参考>
 Google Mapの各種アイコンはここに一覧表があり、URLも記載されています。
 こうした企業のマーカー画像を利用すると、一方的に削除されたりしたらアウトです。自作してご自分のサーバに置くことをお薦めします。
// マーカー関連
// 繰り返し処理
var vectorSource = new ol.source.Vector({});
var places1 = [
   [ 130.55702877229896, 31.596831484278287, '鹿児島市役所', 1 ],
   [ 130.54173481650463, 31.583698750408683, '鹿児島中央駅', 2 ],
     ・
     ・
ここにマーカーを置く場所の経度・緯度・名称・マーカーの色番号をコンマで区切って列挙します。
文字列はシングルクォーテーションで囲みます。
     ・
     ・
   [ 130.55846107191587, 31.560152070056056, '鹿児島県庁', 3 ]
];
var marker1=[];
for (var i = 0; i < places1.length; i++) {
  marker1[i] = new ol.Feature({
    geometry: new ol.geom.Point(ol.proj.fromLonLat([places1[i][0], places1[i][1]])),
    name: places1[i][2] 
  });
var icontype = new ol.style.Style({
    image: new ol.style.Icon(({
          anchor:[0.5,1],
          anchorXUnits: 'fraction',
          anchorYUnits: 'fraction',
      src: 'https://www.sci.kagoshima-u.ac.jp/oyo/advanced/symbol/nred_Marker' + places1[i][3] + '.png'
// マーカー画像の指定
    }))
  });
 marker1[i].setStyle(icontype);
  map.addLayer(new ol.layer.Vector({source: new ol.source.Vector({
    features: [marker1[i]]
  })}));
}

  //鉱山アイコン(個別追加の例)
  var icontype2 = new ol.style.Style({
    image: new ol.style.Icon(({
          anchor:[0.5,1],  // アンカーの位置はマーカーの最下位中央
          anchorXUnits: 'fraction',
          anchorYUnits: 'fraction',
      src: 'https://www.sci.kagoshima-u.ac.jp/oyo/advanced/symbol/mine.png'
// マーカー画像の指定
    }))
  });
  marker2 = new ol.Feature({
    geometry: new ol.geom.Point(ol.proj.fromLonLat([130.45107078738013, 31.4855148230373])),
    name: '錫山鉱山'
  });
 marker2.setStyle(icontype2);

  map.addLayer(new ol.layer.Vector({source: new ol.source.Vector({
    features: [marker2]
  })}));
もちろん、マーカー画像は自作でも結構です。その場合には、相対アドレスの指定で構いません。マーカー数が少ない場合には、繰り返し処理のような凝ったことをしなくても、鉱山の例にようにして1個ずつ逐次追加してもよいと思います。

 ポップアップ

 ポップアップは簡単なコマンド一つで実現できる代物ではなさそうです。その代わり <style> で、吹き出しの形や大きさ・位置など自由に変えられます。どの数字を変えるとどこが変わるか試行錯誤してみてください。
 <style> ~ </style> は <script> の前におきます。
マーカーをクリックすると名称と写真がポップアップします
サンプルプログラムのURL
<body>
<div id="map" class="map" style="width: 500px; height: 400px;">
<div id="popup" class="ol-popup">
  <a href="#" id="popup-closer" class="ol-popup-closer"></a>
  <div id="popup-content"></div>
</div>
</div>

 <style>
  /*吹き出し用のスタイル*/
/*ポップアップ設定*/
  .ol-popup {
     position: absolute;
     background-color: white;
     -webkit-filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
     filter: drop-shadow(0 1px 4px rgba(0,0,0,0.2));
     padding: 10px;
    border-radius: 10px;
    border: 1px solid #cccccc;
    bottom: 15px;
    left: -50px;
    min-width: 150px;
  }
  .ol-popup:after, .ol-popup:before {
     top: 100%;
     border: solid transparent;
     content: " ";
     height: 0;
     width: 0;
     position: absolute;
     pointer-events: none;
  }
  .ol-popup:after {
     border-top-color: white;
     border-width: 10px;
     left: 48px;
     margin-left: -10px;
  }
  .ol-popup:before {
     border-top-color: #cccccc;
     border-width: 11px;
     left: 48px;
     margin-left: -11px;
  }
/*ポップアップ閉じるボタン*/
  .ol-popup-closer {
     text-decoration: none;
     position: absolute;
     top: 2px;
     right: 8px;
  }
  .ol-popup-closer:after {
     content: "×";
  }
 </style>

   <script>
  //吹き出し用オーバレイ準備
  var container = document.getElementById('popup');
  var content = document.getElementById('popup-content');
  var closer = document.getElementById('popup-closer');
  var popoverlay = new ol.Overlay({
    element: container,
    autoPan: true,
    autoPanAnimation: {
      duration: 250
    }
  });
  //吹き出し閉じる用
  closer.onclick = function() {
    popoverlay.setPosition(undefined);
    closer.blur();
    return false;
  };  

// 地理院地図
     ・
     ・
      var map = new ol.Map({
        layers: [gsi_layer,gsj_layer],
        target: document.getElementById('map'),
        controls: ol.control.defaults().extend([new ol.control.FullScreen(), new ol.control.ScaleLine()]),
        overlays: [popoverlay],  // オーバーレイを追加
        view: new ol.View({
          center: ol.proj.fromLonLat([130.55213642306887, 31.583351443529075]),
          zoom: 13
        })
      });

// マーカー関連
var vectorSource = new ol.source.Vector({});
var places3 = [
   [ 130.54462623782496, 31.569924298615028, '鹿大郡元キャンパス','kadai_campass.jpg',1 ],
   [ 130.5574525613288, 31.568169212333785, '鹿大荒田キャンパス','kadai_arata.jpg',2 ],
   [ 130.55271578021063, 31.5939619983321, '鹿児島県立博物館','pref_museum.jpg',3 ]
];
var marker3=[];
for (var i = 0; i < places1.length; i++) {
  marker1[i] = new ol.Feature({
    geometry: new ol.geom.Point(ol.proj.fromLonLat([places3[i][0], places3[i][1]])),
    name: "<div><small>" + places3[i][2] + "</small><br><img src='../image/" + places3[i][3] + "' width='100'></div>"
// 写真の存在するフォルダを含め相対アドレスで指定
// 絶対アドレスなら、この場合、https://www.sci.kagoshima-u.ac.jp/oyo/advanced/image/ となります
  });
var icontype = new ol.style.Style({
    image: new ol.style.Icon(({
          anchor:[0.5,0.5],  // アンカーの位置はマーカーの中央
          anchorXUnits: 'fraction',
          anchorYUnits: 'fraction',
      src: '../symbol/LandMark1' + places3[i][4] + '_S.png'
// マーカー画像の指定
    }))
  });
 marker3[i].setStyle(icontype);
  map.addLayer(new ol.layer.Vector({source: new ol.source.Vector({
    features: [marker3[i]]
  })}));
}
  //吹き出しを表示するためのクリックイベント
  map.on(['click'], function(evt) {
    var feature = map.forEachFeatureAtPixel(evt.pixel,function(feature) {return feature;});
    if (feature) {
      content.innerHTML = feature.get('name');
      popoverlay.setPosition(evt.coordinate);
      map.getViewport().style.cursor = 'pointer';
    } else {
      popoverlay.setPosition(undefined);
      map.getViewport().style.cursor = 'inherit';
    }
  });
   </script>

 クリック位置の座標

 地図で緯度経度を知りたいことがあります。今度はJQuery.jsとBootstrap.jsも使いましょう。<head></head>の中に追加しておきましょう。ポップアップの応用編です。
地図をクリックするとその点の緯度経度が分かります
サンプルプログラムのURL
<head>
 ・
 ・
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
</head>
<body>
<div id="map" class="map" style="width: 500px; height: 400px;"></div>
    <div style="display: none;">
      <!-- Popup -->
      <div id="popup" title="クリック位置:"></div>
    </div>
    <style>
      .popover-content {
        min-width: 150px;
      }
    </style>
   <script>

 以下をvar map文の後</script>の前に記述します。
  // クリックイベント
      var popup = new ol.Overlay({
        element: document.getElementById('popup')
      });
      map.addOverlay(popup);

      map.on('click', function(evt) {
        var element = popup.getElement();
        var coordinate = ol.proj.transform(evt.coordinate, "EPSG:3857", "EPSG:4326");
        var hdms = ol.coordinate.toStringHDMS(coordinate)
//     var lon = Math.round(coordinate[0] * 100000) / 100000;  // 経度
//     var lat = Math.round(coordinate[1] * 100000) / 100000;  // 緯度
        $(element).popover('destroy');
        popup.setPosition(coordinate);
        $(element).popover({
          'placement': 'top',
          'animation': false,
          'html': true,
          'content': '' + hdms + ''
//          'content': '' + lon + ', ' + lat + ''
        });
        $(element).popover('show');
      });
 なお、hdmsで緯度経度を度分秒表示に変換しています。十進法のままが良い時には、hdmsの代わりにcoordinateをそのまま使ってください。その場合、15桁まで表示されますが、精度を考えると無駄です。数字を丸める関数Math.round()を使うのも一法です。上記プログラムのうち、//を付けて備考にしてある3行の//を消去し、hdmsの記載がある2行に//を付けてください。緯度経度は小数点以下5桁で四捨五入しています。この時、<style>の「min-width:」を180px;くらいにすると、1行表示になります。

 中心位置の座標

 Web-GISの作業では、十進法の経度緯度を大量に求める必要があります。上のポップアップではコピペできません。十字線のセンターマークに目標値点を合わせて、経度緯度を求めるほうが効率的です。別項の「位置情報取得ツール」はその目的で作りました。ここでは、そのプログラムを紹介しましょう。センターマークは、地図の移動・拡大縮小とは無関係に定位置に常に表示されていなければなりません。そのためには、LayerやOverlayよりもControlのほうが適しています。それを使うことにします。その1は、結果の数字を地図内に表示するやり方(containerを作成)で、その2は欄外に表示するやり方です。別項はその2の方法で作られています。コピペできるからです。
地図中心位置の緯度経度(その1)
サンプルプログラムのURL
地図中心位置の緯度経度(その2)
サンプルプログラムのURL
<その1>
<body>
<div id="coordinates"></div>
<div id="map" id="map" style="width:500px; height:400px"></div>
<style>
   .center-marker { position: absolute; top: 50%; left: 50%; }
   .center-marker a { display: block;
       height: 30px; width: 30px;
       margin-top: -15px;  margin-left: -15px;
       background: url(https://www.sci.kagoshima-u.ac.jp/oyo/advanced/symbol/cross.gif) 50% 50% no-repeat;
   }
#coordinates {
  position: absolute;
  bottom: .5em;
  right: 3em;
  background-color: white;
}
</style>
<script>
window.app = {};
var app = window.app;

// 地理院地図
 ・
 ・
      var map = new ol.Map({
 ・
 ・
// 中央にセンターマーカー
    app.addCenterMarker = function(opt_options) {
	let options = opt_options || {};
	let anchor = document.createElement('a');
	let element = document.createElement('div');
	element.className = 'center-marker ol-unselectable';
	element.id = 'centerMarker';
	element.appendChild(anchor);
	ol.control.Control.call(this, { element: element, target: options.target });
    };
    ol.inherits(app.addCenterMarker, ol.control.Control);
    map.addControl(new app.addCenterMarker());
    document.getElementById('centerMarker').style.display = "block";
// -------------------------------------------------------------------
   $('#coordinates').text([130.54449,31.57016]); // 初期中心座標
 map.on('pointermove', getCenter);
 $('#coordinates').appendTo(
   $('.ol-overlaycontainer')
 );
 function getCenter (evt) {
// 新しい中心位置の取得
   var coord = ol.proj.transform(evt.map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326');
     var lon = Math.round(coord[0] * 100000) / 100000;
     var lat = Math.round(coord[1] * 100000) / 100000;
    var out = lon + ',' + lat;
//   var out = ol.coordinate.toStringHDMS(coord);
   $('#coordinates').text(out);
 }
<その2>
 その2はもっと簡単で、containerを作る必要がありません。<body>に、<div id="msg"></div>を追加します。
もちろん、<div id="coordinates"></div>と、</style>の#coordinates関連項は削除し、その上で、末尾を次のように変更します。
// -------------------------------------------------------------------
   map.on(['moveend'],function(evt){
// 新しい中心位置の取得
   var coord = ol.proj.transform(evt.map.getView().getCenter(), 'EPSG:3857', 'EPSG:4326');
     var lon = Math.round(coord[0] * 100000) / 100000;
     var lat = Math.round(coord[1] * 100000) / 100000;
      document.getElementById("msg").innerHTML = "(" + lon + "," + lat + ")";
});


 地名検索

 ほとんどの地学の論文には地名が出てきます。こうした論文を読む際には、座右に地質図を置き、geological settingを頭に入れてから読んで欲しいものです。そのためにも地名検索が必要です。住所や施設名などから位置情報を取得するのがgeocoderです。Jonataswalker氏がOpenlayer用のol-geocoderを作成されましたので、使わせていただくことにします。Google MapやYahoo!地図には、所番地まで検索できる住所検索機能が付いていますが、これらは商用なので使えません。ol-geocoderはオープンソースのOpen Street Mapを使っています。大まかな地名しか出てきませんが、これでも上記目的には役立つでしょう。もちろん、地名の場所の位置座標も表示できます(下にその例を示します)が、地名自体が大まかなので、あまり意味がありません。なお、表示する地図は地理院地図など他のものでも構いません。バックでOSMの地名データベースを参照しているだけですから。「位置情報取得ツール」をご覧ください。
地名検索(ルーペ印:検索,日本語可)
サンプルプログラムのURL
地名検索(経度緯度表示)
サンプルプログラムのURL
<head>に下記を追加します。
<link rel="stylesheet" href="https://unpkg.com/ol-popup@4.0.0/src/ol-popup.css">
<script src="https://unpkg.com/ol-popup@4.0.0/dist/ol-popup.js"></script>
<link rel="stylesheet" href="https://unpkg.com/ol-geocoder@latest/dist/ol-geocoder.min.css">
<script src="https://unpkg.com/ol-geocoder"></script>

次いで<script>を下記のようにします。
  var olview = new ol.View({
    center: ol.proj.transform([130.65681767649158, 31.584512174617235], 'EPSG:4326', 'EPSG:3857'),
    zoom: 9
  });
  var baseLayer = new ol.layer.Tile({
    source: new ol.source.OSM(),
  });
  var map = new ol.Map({
    target: document.getElementById('map'),
    controls: ol.control.defaults().extend([new ol.control.FullScreen(), new ol.control.ScaleLine()]),
    view: olview,
    layers: [baseLayer],
  });
  // popup
  var popup = new ol.Overlay.Popup();
  map.addOverlay(popup);
  // Instantiate with some options and add the Control
  var geocoder = new Geocoder('nominatim', {
    provider: 'osm',
    lang: 'jp',  // 使用言語(英語は'en')
    placeholder: '検索:',   // 'Search for ...',
    limit: 5,
    debug: false,
    autoComplete: true,
    keepOpen: true
  });
  map.addControl(geocoder);
  // Listen when an address is chosen
  geocoder.on('addresschosen', function (evt) {
     var center_point = new ol.proj.transform(evt.coordinate, "EPSG:3857", "EPSG:4326");
     console.info(evt);
     window.setTimeout(function () {
      popup.show(evt.coordinate, evt.address.formatted);
// 位置情報表示の場合上の1行を下記と置き換える
//  var coord = new ol.proj.transform(evt.coordinate, "EPSG:3857", "EPSG:4326");
//  var location = evt.address.formatted + "</br>" + ol.coordinate.toStringHDMS(coord);
//  popup.show(evt.coordinate, location);
    }, 3000);
   map.getView().setCenter(new ol.proj.fromLonLat(center_point));
  });

 地図の切り替え

 地図を切り替えて使いたいことがあります。OpenLayers ver.2ではレイヤースィッチャーという機能がありましたが、ver.3からなくなりました。Walkermatt氏が代替のjavascript(ol-layerswitcher.js)を作ってくださいましたので、それを使わせてもらいましょう。さまざまなタイル地図が公開されています。「ラスター地図いろいろ」をご覧ください。ソースコードを読めば各種地図の所在URLが分かります。
地図の切り替え(右上のロゴマークで切り替えます。)
サンプルプログラムのURL
<head>の中に次の2行jsとcssを加えます。
    <script src="https://rawgit.com/walkermatt/ol3-layerswitcher/master/src/ol3-layerswitcher.js"></script>
    <link rel="stylesheet" href="https://rawgit.com/walkermatt/ol3-layerswitcher/master/src/ol3-layerswitcher.css">

<script>
(function() {
    var map = new ol.Map({
        target: 'map',
        controls: ol.control.defaults().extend([new ol.control.FullScreen(), new ol.control.ScaleLine()]),
        layers: [    // var map の後のlayersまで
// Base Map Group
            new ol.layer.Group({
                'title': 'Base maps',
                layers: [
  new ol.layer.Tile({
    'title': '地理院地図',
    'type': 'base',
        visible: false,
        source: new ol.source.XYZ({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院地図</a>"
          })
         ],
         url: "http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"
              })
            }),
 ・
 ・
      ここにベースマップを列挙する。
 ・
 ・
                ]   // Base Map Group のlayers
            }),  // Base Map Group終わり
// Overlay Group
            new ol.layer.Group({
                title: 'Overlays',
                layers: [
  new ol.layer.Tile({
    'title': 'シームレス地質図',
    'type': 'overlay',
        opacity: 0.7,
        visible: true,
        source: new ol.source.XYZ({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://gbank.gsj.jp/seamless/v2/api/1.2/legend.html' target='_blank'>産総研シームレス地質図</a>"
         })
        ],
         url: "https://gbank.gsj.jp/seamless/v2/api/1.2/tiles/{z}/{y}/{x}.png"
              })
            }),
 ・
 ・
      ここにオーバーレイを列挙する。
 ・
 ・
                ]   // Overlay Group のlayers
            })  // Overlay Group終わり
        ],   // var map 後ろのlayersの終わり
// 地図描画
        view: new ol.View({
            center: ol.proj.transform([130.51294, 31.55826], 'EPSG:4326', 'EPSG:3857'),
            zoom: 11
        })
    });
    // LayerSwitcher
    var layerSwitcher = new ol.control.LayerSwitcher({
        tipLabel: 'Légende' // Optional label for button
    });
    map.addControl(layerSwitcher);
})();  // LayerSwitcher終わり

 地質図凡例表示

 産総研のシームレス地質図ver.2には、着色の外に記号も付記されています。凡例のサイトを見れば、その色が何を意味するのか分かります。でも大変煩わしいので、任意の箇所をクリックすると、その地点の地質解説がポップアップする機能が付いています。上述の「ポップアップ」の項で作成した<style>を使いましょう。吹き出しの大きさ等は自由に変えてください。なお、この項目については、産総研西岡芳晴氏のご教示を受けました。記して篤く謝意を表します。
地質図凡例表示(任意の地点をクリックしてください。)
サンプルプログラムのURL
<script>の最初に下記を記入します。
var seamlessServer = 'https://gbank.gsj.jp/seamless/';
var seamlessTileG = seamlessServer + 'v2/api/1.2/tiles/{z}/{y}/{x}.png?layer=g';
var legendHash = {};
var R = 6378137;
  //吹き出し用オーバレイ準備
  var container = document.getElementById('popup');
  var content = document.getElementById('popup-content');
  var closer = document.getElementById('popup-closer');
  var popoverlay = new ol.Overlay({
    element: container,
    autoPan: true,
    autoPanAnimation: {
      duration: 250
    }
  });
  //吹き出し閉じる用
  closer.onclick = function() {
    popoverlay.setPosition(undefined);
    closer.blur();
    return false;
  }; 
次いで、var mapの中に次の1行を追加します。
        overlays: [popoverlay],
地図描画の後に以下を記入します。
  //吹き出しを表示するためのクリックイベント
  map.on(['click'], function(evt) {
    var feature = map.forEachFeatureAtPixel(evt.pixel,function(feature) {return feature;});
    if (feature) {
      content.innerHTML = feature.get('name');
      var coordinate = feature.getGeometry().getCoordinates();
      popoverlay.setPosition(coordinate);
    } else {
var coord = evt.coordinate;
var coordinate =ol.proj.toLonLat( coord );
var z = map.getView().getZoom();
x = ( 0.5 + coord[ 0 ] / ( 2 * R * Math.PI ) ) * Math.pow( 2, z );
y = ( 0.5 - coord[ 1 ] / ( 2 * R * Math.PI ) ) * Math.pow( 2, z );
getPixelValue( seamlessTileG, x, y, z ).then( function( pv ) {
  var s = '';
  if ( pv !== null && legendHash[ pv ] ) {
  s += '<div style="font-size:9px; line-height:12px;">時代: ' + legendHash[ pv ].formationAge_ja + '<br />';
  s += '岩石: ' + legendHash[ pv ].lithology_ja + '</div>';
  content.innerHTML = s;
 popoverlay.setPosition(coord);
  }
} );
    }
  });
function getPixelValue( url, rx, ry, z ) {
	var
		x = Math.floor( rx ),			// タイルX座標
		y = Math.floor( ry ),			// タイルY座標
		i = ( rx - x ) * 256,			// タイル内i座標
		j = ( ry - y ) * 256,			// タイル内j座標
		img = new Image();
	return new Promise( function( resolve, reject ) {
		img.crossOrigin = 'anonymous';	// 画像ファイルからデータを取り出すために必要です
		img.onload = function(){
			var
				canvas = document.createElement( 'canvas' ),
				context = canvas.getContext( '2d' ),
				pv,
				data;
			canvas.width = 1;
			canvas.height = 1;
			context.drawImage( img, i, j, 1, 1, 0, 0, 1, 1 );
			data = context.getImageData( 0, 0, 1, 1 ).data;
			pv = data[ 0 ] * 256 * 256 + data[ 1 ] * 256 + data[ 2 ];
			if ( data[ 3 ] !== 255 || pv === 8388608 ) {
				pv = null;
			}
			resolve( pv );
		}
		img.onerror = function(){
			reject( null );
		}
		img.src = url.replace( '{z}', z ).replace( '{y}', y ).replace( '{x}', x );
	} );
};
$( function(){
	$.getJSON( seamlessServer  + 'v2/api/1.2/legend.json' , function( data ){
		for( v of data ){
			legendHash[ v.r * 256 * 256 + v.g * 256 + v.b ] = v;
		}
	} );
} );

 地質図凡例とポップアップマーカー

地質図凡例とポップアップマーカーの共存
サンプルプログラムのURL
 ポップアップマーカーと地質図凡例表示を共存させることもできます。上述のコードを組み合わせたものですから、ソースコードの記述は省略します。


 標高タイルで標高値を得る

 地学では緯度経度などの位置情報のほか標高データも重要です。西岡・永津(2015)は、タイルをセルに分割し、RGB値の組み合わせで標高を表示する方法を提案しました。標高の元データは基盤地図情報ですが、ここでは全国が整備されている10mメッシュDEM(ズームレベルz=14相当)を使用します。得られる標高は、セルの左上(北西)角の値で、中央値でも平均値でもないことに留意してください。また、ズームレベルはなるべく14をご使用ください。なお、この項目については、産総研西岡芳晴氏のご教示を受けました。記して篤く謝意を表します。プログラムは、ポップアップのプログラムを流用します。吹き出しの位置が正確にクリック地点と一致するよう、スタイルを微調整してください。なお、ここでは産総研の標高タイルを使用していますが、地理院標高タイルを使いたい場合には、// ●の行をその直上の行と入れ替えてください。
標高取得(任意の地点をクリックしてください。)
サンプルプログラムのURL
<script>の最初に次の定数を記入します。
var
        R = 6378137,     // 地球の半径(m)
        map,
        elevServer = 'https://tiles.gsj.jp/tiles/elev/gsidem/',
        elevTile = elevServer + '{z}/{y}/{x}.png';
// ●  elevServer = 'https://cyberjapandata.gsi.go.jp/xyz/dem_png/',
// ●  elevTile = elevServer + '{z}/{x}/{y}.png';
次に、var mapの項の後に、下記を記入します。
function getElev( rx, ry, z, then ) {
      var
          x = Math.floor( rx ),			// タイルX座標
          y = Math.floor( ry ),			// タイルY座標
          i = ( rx - x ) * 256,			// タイル内i座標
          j = ( ry - y ) * 256,			// タイル内j座標
              img = new Image();
              img.crossOrigin = 'anonymous';
              img.onload = function(){
                      var
                         canvas = document.createElement( 'canvas' ),
                         context = canvas.getContext( '2d' ),
                         h = 'e',
                         data;
                     canvas.width = 1;
                     canvas.height = 1;
                     context.drawImage( img, i, j, 1, 1, 0, 0, 1, 1 );
                     data = context.getImageData( 0, 0, 1, 1 ).data;
		if ( data[ 3 ] === 255 ) {
			h = data[ 0 ] * 256 * 256 + data[ 1 ] * 256 + data[ 2 ];
			h = ( h < 8323072 ) ? h : h - 16777216;
			h /= 100;
		}
		then( h );
	}
	img.src = elevServer + z + '/' + y + '/' + x + '.png?res=cm';
// ●	img.src = elevServer + z + '/' + x + '/' + y + '.png?res=cm';
};
map.on( 'click', function( event ){
     var
	z = map.getView().getZoom(),
	coord = event.coordinate,
	lonLat =ol.proj.toLonLat( coord );
	x = ( 0.5 + coord[ 0 ] / ( 2 * R * Math.PI ) ) * Math.pow( 2, z );
	y = ( 0.5 - coord[ 1 ] / ( 2 * R * Math.PI ) ) * Math.pow( 2, z );
	e = event;
	getElev( x, y, z, function( h ) {
                    var
		s;

		s = '緯度: ' + lonLat[ 1 ].toFixed( 5 ) + '
'; s += '経度: ' + lonLat[ 0 ].toFixed( 4 ) + '
'; s += '標高: ' + ( ( h =='e' ) ? 'データなし' : h + 'm' ); // ● s += '標高: ' + ( ( h == -83886.08 ) ? 'データなし' : h + ' m' ); content.innerHTML = s; popoverlay.setPosition(coord); } ); popoverlay.setPosition(undefined); //マーカ以外の場所をクリックしたら吹出を消す map.getViewport().style.cursor = 'inherit'; } );

 地理院地図vectorの表示

 国土地理院はベクトル地図の試験配信を始めました。当初はgeojsonだったのですが、今度からpbfフォーマットProtocolbuffer Binary Formatで提供されています。スタイルを地物ごとに変えれば、より地図らしくなりますが、今回はデフォルトのままとしておきます。スタイルを変える場合には、style: style1の前の「//」を削除します。ただし、これは全部を一括変更するだけです。地物ごとに変えるには、地理院提供のstyle.jsonを使ってください。
地理院地図vector
サンプルプログラムのURL
地理院地図vector(水系・等高線・道路・鉄道・建物を抽出)
サンプルプログラムのURL
      var style1 = new ol.style.Style({
          fill: new ol.style.Fill({
              color: 'rgba(0,255,255,0.2)',
          }),
        stroke: new ol.style.Stroke({
          color: 'rgb(0,255,255)',
          width: 1
        })
      });
     var bvmap_layer = new ol.layer.VectorTile({
          source: new ol.source.VectorTile({
        attributions: [
          new ol.Attribution({
            html: "<a href='https://github.com/gsi-cyberjapan/gsimaps-vector-experiment' target='_blank'>地理院地図vector</a>"
          })
         ],
            format: new ol.format.MVT(),
            url: 'https://cyberjapandata.gsi.go.jp/xyz/experimental_bvmap/{z}/{x}/{y}.pbf'
          }),
//          style: style1
     });

      var map = new ol.Map({
        layers: [gsi_layer,bvmap_layer],
 ・
 ・
 もちろん、layersを指定すれば、特定の地物だけを抽出することも可能です。 'waterarea' , 'river', 'contour' , 'road', 'railway'などなど…。
formatのところを下記のように改変します(水系waterareaの場合)。複数指定も可能です。
     var waterarea_layer = new ol.layer.VectorTile({
 ・
 ・
    format: new ol.format.MVT({
      layers: ['waterarea']
    }),

      var map = new ol.Map({
        layers: [waterarea_layer],
 ・
 ・
 地物ごとにstyleで色を変えれば、より地図らしくなります。下図は、水系・等高線・道路・鉄道・建物を抽出し、地理院地図に近い色を当てはめてみました。もちろん、地理院地図vectorは同じ地物でも細分されていますから、これとは異なります。

<注> 地図タイルについて

ズームレベルとタイル座標(国土地理院)
 ご承知のように地球は略々球体です。それを二次元平面に表現するにはさまざまな方法が考案されています。地質学でよく使われているのがステレオ投影(等角投影)やシュミット投影(等面積投影)です(かだいおうち Original「ステレオグラフ」参照)。地図とくに海図ではメルカトル図法(正角円筒図法)が用いられます。地球儀を地軸に平行で赤道に接する円筒に投影したものです。このため、経線は地軸に平行な平行直線になりますし、緯線は経線に直交し、赤道と同じ長さの平行直線になります。船の航程線が正しく表現されるので、航海には昔から利用されてきました。難点は極地方が不自然に大きく表現されることです。
 Googleは、緯度約85.0511度以上を切り捨てた地域を1枚の正方形地図(タイル)で表現し、これをズームレベル0としました。不自然なところはカットしてしまったのです。このタイルの中心は経度0度、緯度0度とします。したがって、左上の点は、経度-180度、緯度約85度で、右下の点は経度180度、緯度約-85度となります。タイルの大きさは256ピクセル×256ピクセルでどれも同じ大きさとします。ズームレベル1はレベル0のタイルを縦横1/2に分割したものです。以下、図に示すように倍々と拡大されて行きます。タイル座標はズームレベルで違いますから、ズームレベルごとに番号を付け、{z}/{x}/{y}.{ext}の順に並べます。{ext}は拡張子で、普通はpngです(空中写真の場合にはjpgを使うこともありますからご注意を)。鹿児島大学付近の例を挙げれば下図のようになります。
 この方法は極めて単純明快で便利ですから、今では事実上の世界標準 de facto standard になり、国土地理院や産総研など日本の政府機関でも採用しています。なお、従来はWMSという方式で配信されていましたが、これはリクエストに応じて、要求された範囲の画像をその都度サーバ側で準備する方式でしたから、サーバ・クライアント双方のマシンパワーが要求されました。その点、このタイル配信は事前に準備されているタイルを送信するだけですから、軽快に動きます。これもこのタイル方式の採用が進んだ原因の一つでしょう。
蛇足:これらのタイルはラスターデータですが、そろそろベクトルデータのネット配信の時代になりつつあるようです。「ベクトル地図いろいろ」をご覧ください。
https://cyberjapandata.gsi.go.jp/
xyz/std/8/220/104.png
https://cyberjapandata.gsi.go.jp/
xyz/std/11/1766/834.png
https://cyberjapandata.gsi.go.jp/
xyz/std/14/14133/6676.png
https://cyberjapandata.gsi.go.jp/
xyz/std/17/113065/53411.png
 上記のことを一目で理解させる機能ol.tilegrid.createXYZがOpenLayersに備わっていることを見つけました。当初、埼玉大学谷謙二教授のLeaflet版を引用していましたが、差し替えます。
地図タイル(Z, X, Yの順)


文献
  1. 西岡芳晴・長津樹理(2015), PNG標高タイル-Web利用に適した標高ファイルフォーマットの考察と実装-. 情報地質, Vol.26, No.4, p.155-163.
  2. (), . , Vol., No., p..
  3. (), . , Vol., No., p..

参考サイト:



初出日:2020/09/15
更新日:2020/12/29