2014年9月16日火曜日

OpenLayers 3 を使ってみよう(その12:KML+マーカーで zoom の自動調整)

これはOpenLayers 3 を使ってみよう(その11:KML から描画した経路とマーカーの表示/非表示の個別切替)からの続きである。
OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。
ここでは OpenLayers 3.7.0 を使っている。
追記)zoom の自動調整については,この次のその13:センターマークと凡例の表示・KML による経路+マーカーの場合の最後の方に別の簡素化した方法について書いてあるので,参照してみて欲しい)
 前回(その11:KML から描画した経路とマーカーの表示/非表示の個別切替)では, KML ファイルから描画した地図上のマーカーや経路を個別に表示したり非表示にしたりするボタンをつけた。 今回は KML ファイルから作った経路に対して,地図の表示範囲を自動で調整してみよう。 KML ファイルについての説明はOpenLayers 3 を使ってみよう(番外2:KML ファイルのフォーマットについて)に書いているので,そちらを見てみて欲しい。

 地図の表示範囲を自動調整に関しては,ol.interaction.Draw で描画した経路から経路情報を取り出して表示範囲の自動調整を行う方法をその8:ol.interaction.Draw 修正可能バージョンで表示領域の大きさの調整を行うで記述した。 ここでは KML ファイルから読み込んだ経路に対して同じ表示範囲の自動調整を行ってみた。 ただし,KML ファイルを読み込んだ時点で自動で表示範囲を調整してみたが,何故かうまくいかなかった。 ということで,ここでは「ボタン」を押して初めて表示範囲の自動調整するようにしている。

 まず,いつものように以下に web ページのソースを載せる。 ここでは前回からの変更箇所の色を変えている。 説明はソースコードに下に書こう。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta http-equiv="content-style-type" content="text/css">
<meta http-equiv="content-script-type" content="text/javascript">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" href="http://openlayers.org/en/v3.7.0/css/ol.css" type="text/css">
<script src="http://openlayers.org/en/v3.7.0/build/ol.js" type="text/javascript"></script>
<style type="text/css">
   div.fill {width: 100%; height: 100%;}

   body {padding: 0; margin: 0;}
   html, body, #map {height: 100%; width: 100%;}
   .ol-attribution {
     padding: 3px;  position: absolute;  background-color:#ffffff;
     background-color:rgba(230,255,255,0.7);
     right: 3px;  bottom:5px;  font-size:12px;
   }
   .ol-attribution ul { padding: 0px;  line-height: 14px;  margin: 0px; }
   .ol-attribution li { line-height: inherit;  display: inline;  list-style: none outside none; }

   .ol-zoom .ol-zoom-out { margin-top: 202px; }
   .ol-zoomslider { background-color: transparent; top: 2.3em; }
   .ol-touch .ol-zoom .ol-zoom-out { margin-top: 212px; }
   .ol-touch .ol-zoomslider { top: 2.75em; }

   .ol-popup { display: none; position: absolute; background-color: white;
     -moz-box-shadow: 0 1px 4px rgba(0,0,0,0.2);
     -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: 5px; border-radius: 10px; border: 1px solid #cccccc; bottom: 24px; left: -51px; }
   .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: " ✖ "; }

   button.boldblack { color:black; font-weight:bold; }
   button.red { color:red; }
   button.boldred { color:red; font-weight:bold; }
   button.blue { color:blue; }
   button.boldblue { color:blue; font-weight:bold; }
</style>
<title>OpenLayers 3 Example: KML Layer Visible/Invisible Switch</title>
<script src="ol3ex12.js" type="text/javascript"></script>
<script src="../jquery.min.js" type="text/javascript"></script>
</head>

<body onload="init_map()">
<div id="map_canvas" style="width: 100%; height: 97%; position:absolute; top:25px; left:0px; font-size:100%;">
  <div id="popup" class="ol-popup">
    <a href="#" id="popup-closer" class="ol-popup-closer"></a>
    <div id="popup-content"></div>
  </div>
</div>

<div style="font-size:85%"> 不透明度:<a title="decrease opacity" href="javascript: directSetOpacity(0.1);">0.1</a> 
  <a title="decrease opacity" href="javascript: directSetOpacity(0.5);">0.5</a> 
  <a title="decrease opacity" href="javascript: directSetOpacity(1.0); ">1.0</a>  
  <b>&nbsp;不透明度・0.2ずつ変更:
    <a title="decrease opacity" href="javascript: changeOpacity(-0.2);">&lt;&lt;</a>
    <span id="opacity_control">0.5</span>
    <a title="increase opacity" href="javascript: changeOpacity(0.2);">&gt;&gt;</a></b>

   <button id="kml_vector_visible" onclick="kml_vector_visible();" class="boldblack">ルート表示</button>
  <button id="kml_vector_invisible" onclick="kml_vector_invisible();" class="red">ルート非表示</button>
   <button id="wp_vector_visible" onclick="wp_vector_visible();" class="boldblack">マーカー表示</button>
  <button id="wp_vector_invisible" onclick="wp_vector_invisible();" class="red">マーカー非表示</button>

   <button id="getLengthButton" onclick="length_line();" class="red">経路長の表示</button>
  <span id="outStr"></span>
   <button id="autoZoomButton" onclick="setCenterZoom();" class="blue">Zoom 自動設定</button>
  <span id="outStr2"></span>
   <span id="outStr3"></span>
</div>
</body>
</html>
 今回の web ページ上の変更点は,自動調整と経路距離の表示のボタンを追加した点である。 それ以外の設定は特に変更はしていない。
 注意点として JavaScript の中で使いたいので jQuery (jquery.min.js) を読み込ませている。 ここでは jQuery はサーバーにダウンロードしてきたものを読み込ませている。

 次に JavaScript を載せよう。ここでも変更箇所の色を変え,説明は web ページのソースと同じくこの下に書いておく。
// -------------------------------------------------------------------
var map = null;        // map 変数
var view = null;       // view 変数
var cyberJ = null;     // 地理院地図用の変数
var kml_vector = null; // KML ファイル用変数 1
var wp_vector = null;  // waypoint KML ファイル用変数 2

var center_lon = 136.181193333; // 中心の経度(大杉谷,宮川第三発電所)
var center_lat = 34.2329783333; // 中心の緯度(大杉谷,宮川第三発電所)

var kml_url  = "work/map_data1.kml";
var kml_url2 = "work/wp_data1.kml";

var initZoom = 15; // ズームの初期値
var MinZoom  = 6;   // ズームの最小値(最も広い範囲)
var MaxZoom  = 21;  // ズームの最大値(最も狭い範囲)

var initPrecision = 8; // 座標表示の小数点以下の桁数の初期値
var initOpacity = 1.0; // 不透明度の初期値
var gMaxOpacity = 1.0; // 不透明度の最大値
var gMinOpacity = 0.0; // 不透明度の最小値
// -------------------------------------------------------------------
// ヒュベニの公式で緯度・経度から距離を求めるための定数
var long_r = 6378137.000;     // [m] 長半径
var short_r = 6356752.314245; // [m] 短半径
var rishin = Math.sqrt((long_r * long_r - short_r * short_r)/(long_r * long_r)); // 第一離心率
var a_e_2 = long_r * (1-rishin * rishin);  // a(1-e^2)
var pi = 3.14159265358979;    // Pi
// *******************************************************************
function init_map() {
// 以下の DOM の定義は,init_map() の中に入れないとだめだった。
    var container = document.getElementById('popup');
    var content   = document.getElementById('popup-content');
    var closer    = document.getElementById('popup-closer');

// 表示用の view 変数の定義
    view = new ol.View({maxZoom: MaxZoom, minZoom:MinZoom});
// cyberJ の opacity をいじるために,cyberJ という変数に入れている。
    cyberJ = new ol.layer.Tile({
        opacity: initOpacity,
        source: new ol.source.XYZ({
            attributions: [ new ol.Attribution({ html: "<a href='http://maps.gsi.go.jp/development/ichiran.html' target='_blank'>国土地理院</a>" }) ],
            url: "http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",  projection: "EPSG:3857"
        })
    });
// 経路の KML データ
// v3.11.0 以降の場合は new ol.format.KML() を new ol.format.KML({ showPointNames: false }) とする。
    kml_vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: kml_url, format: new ol.format.KML() }) });
// マーカーの KML データ
// v3.11.0 以降の場合は new ol.format.KML() を new ol.format.KML({ showPointNames: false }) とする。
    wp_vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: kml_url2, format: new ol.format.KML() }) });
// -------------------------------------------------------------------
// 地図をクリックした際に,停留点の情報を表示するための overlay 変数(popup 用)
    var overlay = new ol.Overlay({ element: container });
// 地図変数 (map 変数) の定義。地理院地図を表示するように指定している
    map = new ol.Map({
        target: document.getElementById('map_canvas'),
        layers: [cyberJ, kml_vector, wp_vector],
        view: view,
        overlays: [overlay],
        renderer: ['canvas', 'dom'],
        controls: ol.control.defaults().extend([new ol.control.ScaleLine()]),
        interactions: ol.interaction.defaults()
    });

    function displayFeatureInfo(pixel, coordinate) {
        var features = [];
        map.forEachFeatureAtPixel(pixel, function(feature, wp_layer) {
            features.push(feature);
        });
        if (features.length > 0) {
            var info = [];
            info.push('<div id="wp_desc" style="font-size:12px; width:215px">'+features[0].get('name')+features[0].get('description')+'</div>');
            overlay.setPosition(coordinate);
            content.innerHTML = info[0];
            container.style.display = 'block';
        } else {
            content.innerHTML = '';
            container.style.display = 'none';
        }
    };
    map.on('click', function(evt) { displayFeatureInfo(evt.pixel, evt.coordinate); });

// マーカー上でアイコンの表示を変更するイベントハンドラー, jQuery が必要
    $(map.getViewport()).on('mousemove', function(e) {
        var pixel = map.getEventPixel(e.originalEvent);
        var hit = map.forEachFeatureAtPixel(pixel, function(feature, layer) { return true; });
        if (hit) { map.getTarget().style.cursor = 'pointer'; }
            else { map.getTarget().style.cursor = ''; }
    });
// popup を閉じるためのイベントハンドラー
    closer.onclick = function() {
        container.style.display = 'none';
        closer.blur();
        return false;
    };
// zoom slider の追加
    map.addControl(new ol.control.ZoomSlider());
// 中心の指定。view に対して指定。transform を忘れないこと。
    view.setCenter(ol.proj.transform([center_lon, center_lat], "EPSG:4326", "EPSG:3857"));
// zoom の指定。view に対して指定する。
    view.setZoom(initZoom);
// span opacity_control (地理院地図の不透明度) に初期値(実数)を入れる。
    document.getElementById('opacity_control').innerHTML = initOpacity.toFixed(1);
} // init_map()
// *******************************************************************
// 地理院地図 (var cyberJ) の opacity(この場合は不透明度) を変える
function changeOpacity(opacity) {
    var newOpacity = (parseFloat(document.getElementById('opacity_control').innerHTML) + opacity).toFixed(1); // 新しい opacity の値を求める
    newOpacity = Math.min(gMaxOpacity, Math.max(gMinOpacity, newOpacity)); // 最大値と最小値の範囲を超えないように
    cyberJ.setOpacity(newOpacity); // 地理院地図の opacity の変更
    document.getElementById('opacity_control').innerHTML = newOpacity.toFixed(1); // opacity の数字の表示書き換え
}
function directSetOpacity(opacity) {
    cyberJ.setOpacity(opacity);
    document.getElementById('opacity_control').innerHTML = opacity.toFixed(1);
}  
// *******************************************************************
function kml_vector_visible()   { kml_vector.setVisible(true); }
function kml_vector_invisible() { kml_vector.setVisible(false); }
function wp_vector_visible()    {  wp_vector.setVisible(true); }
function wp_vector_invisible()  {  wp_vector.setVisible(false); }
// *******************************************************************
// ヒュベニの公式を使った距離計算
// 2点間の距離
function dist_2pts(lon0, lat0, lon1, lat1) {
    lon0 = lon0 * pi / 180;  lat0 = lat0 * pi / 180; // in radian
    lon1 = lon1 * pi / 180;  lat1 = lat1 * pi / 180; // in radian
    var d_lon = lon1 - lon0;
    var d_lat = lat1 - lat0;
    var ave_lat = (lat1+lat0)/2;
    var Wx = Math.sqrt(1-rishin * rishin * Math.sin(ave_lat) * Math.sin(ave_lat));
    var Mx = a_e_2 /Wx/Wx/Wx;
    var Nx = long_r /Wx;
    var dum = (d_lat * Mx)*(d_lat * Mx) + (d_lon* Nx * Math.cos(ave_lat)) * (d_lon* Nx * Math.cos(ave_lat)); // square of distance
    return Math.sqrt(dum);
}
// ===================================================================
// 経路の長さを求めて表示する
function length_line() {
    var lineLength = 0;
    var sourceKML = kml_vector.getSource();
    var features = sourceKML.getFeatures(); // 通常の JavaScript の array
    var lengf = features.length;
    for (var j=0; j < lengf; j++) {
        var geometry = features[j].getGeometry(); // featureOverlay の中のgeometry(例:ol.geom.LineString )
        var coordArray = geometry.getCoordinates(); // geometry 変数の中の座標を配列へ(メルカトル座標系)
        for (var i=0; i < coordArray.length; i++) { coordArray[i] = ol.proj.transform(coordArray[i],"EPSG:3857", "EPSG:4326"); }
        for (var i=0; i < (coordArray.length-1); i++) { lineLength = lineLength + dist_2pts(coordArray[i][0],coordArray[i][1],coordArray[i+1][0],coordArray[i+1][1]); }
    }
    document.getElementById("outStr").innerHTML = "&nbsp;&nbsp;L = "+(Math.floor(lineLength)/1000)+" [km]";
}
// *******************************************************************
function setCenterZoom() {
    var sourceKML = kml_vector.getSource();
    var features = sourceKML.getFeatures(); // 通常の JavaScript の array
    var lengf = features.length;
    document.getElementById("outStr2").innerHTML = ""; // 文字列のクリア
// feature が1個以上,feature[0] が2点以上あれば,以下の処理を行う
    if ((lengf > 0) && (features[0].getGeometry().getCoordinates().length >1)) {
        var coordArray = new Array();
        for (var j=0; j < lengf; j++) {
            coordArray = coordArray.concat(features[j].getGeometry().getCoordinates()); // 各 feature に含まれる線分座標の配列を追加
        }
        var lineStrings = new ol.geom.LineString(coordArray);
        view.fit(lineStrings, map.getSize());
    }
}
// *******************************************************************
 今回の変更点は,map と view という2つの変数をグローバルに定義し直している点と, ヒュベニの公式を使った距離計測,そして最後に緑色で表示したsetCenterZoom()を追加した点である。 基本的な事はその8:ol.interaction.Draw 修正可能バージョンで表示領域の大きさの調整を行うを見てほしい。

 その8:ol.interaction.Draw 修正可能バージョンで表示領域の大きさの調整を行うとここでの違いは,ol.interaction.Draw の描画データから経路を取り出すのではなく,読み込んだ KML ファイルの経路データを取り出している点である。 KML ファイルの経路データを読み出すには,まず var sourceKML = kml_vector.getSource(); として,KML layer の source を取り出す。 そこから,var features = sourceKML.getFeatures(); で feature の配列を取り出し,var coordArray = features[j].getGeometry().getCoordinates(); でその feature に含まれる経路を取り出す。

 ol.interaction.Draw の時との違いは,var features = sourceKML.getFeatures(); で取り出した feature の配列が,ol.Collection ではなく,通常の JavaScript の配列となっている点である。そのため,配列の長さを求める手続きや,配列の要素の取り出しが違っている点である。 それ以外は同じルーチンを使っている。

 「その12」のサンプルを具体的な web ページとして用意したので,具体的な表示を見てみて欲しい。(ちなみにサンプルページはアクセスログを取るルーチンを組み込んでいます)

その13:センターマークと凡例の表示・KML による経路+マーカーの場合に続く

0 件のコメント: