2016年7月3日日曜日

OpenLayers 3 を使ってみよう(その18:Marker アニメーション)

 これは,OpenLayers 3 を使ってみよう(その17:OpenLayers v3 で 経路の距離を測る)からの続きになる。 OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。
 その13までのページでは OpenLayers v3.7.0 で書いてきたが,ここでは OpenLayers 3.16.0 で書かれている。
 前回(その17:OpenLayers v3 で 経路の距離を測る)は,OpenLayers の Example で見つけた Measure Example の情報から,距離の計算について書いてみた。 地図上の距離の計算には,個人的にはヒュベニの公式を使っていこうと思っているが,haversine 関数を用いるのも一計かもしれないと感じている。

 今回も OpenLayers の Example から,アニメーションのサンプル Marker Animation を見つけたので,挑戦してみた。 地図でのアニメーションはいつくか考えられるが,例えば,地図を回すとか,拡大・縮小をアニメーションで行うとかも考えられる。 ここでは,経路のスタートからゴールまでを黒い丸が進んでいく,というアニメーションである。 それを組み込んでみた。

 ポイントとしては,ol.render.Event クラスの postcompose というのを使うところ,みたい…(そんなにしっかりとわかっているわけじゃない…)。 後で示す JavaScript ファイルの最後の方にある「function startAnimation()」と「function stopAnimation()」という中で,postcompose というのが使われている。 そこでは「moveFeature()」という関数を呼び出して,実際に黒丸を動かしているみたい。

 それ以外のポイントとしては,サンプルと違って JavaScript ファイルを <body> の前に読み込ませている。 そのため,<body> を読み込んだ際に実行する init_map() という関数を定義している。 また,init_map() 以外で使う変数は global に定義しないといけなかった。 さらに,経路は KML 形式のファイルを読み込ませているが,読み込ませた KML ファイルのデータを使うには,
kml_vector.once('change',function() {
.....
}
のように,イベント処理を使わないとうまく処理ができなかった。

 まず,いつものように以下に web ページのソースを載せよう。 このソースは,その13:センターマークと凡例の表示・KML による経路+マーカーの場合最後のサンプルにアニメーションの効果を付加している。 サンプル13に関する部分はサンプル13での説明を見て欲しい(なんかズボラやなぁ…)。
 web のソース部分も JavaScript の部分も,基本的に青い部分がサンプル13からの変更点,つまりはアニメーションに関連する部分を表している。 説明はソースコードに下に書こう。

<!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.16.0/css/ol.css" type="text/css">
<script src="http://openlayers.org/en/v3.16.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%;}

   .center-marker {
       position: absolute;
       top: 50%; left: 50%;
   }
   .center-marker a {
       display: block;
       height: 32px; width: 32px;
       margin-top: -16px;  margin-left: -16px;
       background-image:url(../cj_map/crosshairs.png);
   }
   .title-fig {
       position: absolute;
       top: 0px; left: 40px;
   }
   .title-fig a {
       display: block;
       height: 21px; width: 163px;
       margin-top: 0px;  margin-left: 0px;
       background-image:url(./cjmap_title.png);
   }
   .symbols {
       position: absolute;
       top: 100%; left: 100%;
   }
   .symbols a {
       display: block;
       height: 144px; width: 120px;
       margin-top: -180px;  margin-left: -120px;
       background-image:url(./cjmap_icons.png);
   }

   .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 AutoSize</title>
<script src="ol3ex18.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:30px; 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%">
  <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="blue">表示</button>
  <button id="kml_vector_invisible" onclick="kml_vector_invisible();" class="red">隠す</button>
   マーカー<button id="wp_vector_visible" onclick="wp_vector_visible();" class="blue">表示</button>
  <button id="wp_vector_invisible" onclick="wp_vector_invisible();" class="red">隠す</button>
   センター<button id="centerMarker_visible" onclick="centerMarker_visible();" class="blue">表示</button>
  <button id="centerMarker_invisible" onclick="centerMarker_invisible();" class="red">隠す</button>
   Title/凡例<button id="titleSymbol_visible" onclick="titleSymbol_visible();" class="blue">表示</button>
  <button id="titleSymbol_invisible" onclick="titleSymbol_invisible();" class="red">隠す</button>
   アニメ<button id="start_animation" class="blue">Start Animation</button>
  <label for="speed">
    speed:&nbsp;
    <input id="speed" type="range" min="10" max="999" step="10" value="60">
  </label>

   <button id="autoZoomButton" onclick="setCenterZoom();" class="boldblue">Zoom Fit</button>
  <span id="outStr2"></span>
   <button id="getLengthButton" onclick="length_line();" class="boldblack">経路長</button>
  <span id="outStr"></span>
   <span id="outStr3"></span>
</div>
</body>
</html>
 web ページ上の変更点は,アニメーションの開始ボタン(Start Animation)と,アニメーションを動かすスピード調整のためのスライドバーを追加したぐらいである。 注意点としては,スタートボタンで,
onClick="xxxx();"
のような指定をしてない点がある。 通常,ボタンを押すと,JavaScript で定義された関数を呼び出すことが多い。 ここでも,アニメーションのスタートボタン以外では指定されている。 しかし,アニメーションのスタートボタンの処理については,JavaScript 内のイベント処理で定義されるため,ここには onClick=... という記載がない。 それ以外の点はサンプル13と同じものである。 多少の文言は変えているが…。

 次に JavaScript を載せよう。 ここも変更点のみ青色にしている。 説明は web ページのソースと同じくこの下に書いておく。
// ===================================================================
// Define a namespace for the application.
window.app = {};
var app = window.app; // 中心マークや凡例の表示用

var map = null;        // map 変数
var view = null;       // view 変数
var cyberJ = null;     // 地理院地図用の変数
var kml_vector = null; // KML ファイル用変数
var wp_vector = null;  // waypoint KML ファイル用変数
var kml_extent = null; // KML ファイルの領域範囲用変数
// -------------------------------------------------------------------
// for animation demo
var styles = null;       // styles for geoMarker

var animating = null;    // flag animating or not
var speed, now;
var speedInput = null;   // speed control Element
var startButton = null;  // start button Element

var routeCoords = null;  // route の座標配列
var routeLength = null;  // route の長さ
var geoMarker = null;    // 移動する点のマーカー
var vectorLayer = null;  // geoMarker を入れる vector Layer

var moveFeature = null;  // アニメーション用の関数用の変数
// -------------------------------------------------------------------
var center_lon = 135.083273333333; // 表示中心の経度(デフォルトは始点の経度)
var center_lat = 34.63374; // 表示中心の緯度(デフォルトは始点の緯度)
var kml_url  = "work/map_data20160430.kml";
var kml_url2 = "work/wp_data20160430.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');
// -------------------------------------------------------------------
// for animation 以下の変数は init_map() 内で指定しないと値が入らない。
    animating = false;
    speedInput = document.getElementById('speed');
    startButton = document.getElementById('start_animation');
// -------------------------------------------------------------------
// 表示用の view 変数の定義
// projection はデフォルトの EPSG:3857(球面メルカトル図法)となっている。
    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: "国土地理院" }) ],
            url: "http://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",  projection: "EPSG:3857"
        })
    });
// 経路の KML データ
    kml_vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: kml_url, format: new ol.format.KML({ showPointNames: false }) }) });
// マーカーの KML データ
    wp_vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: kml_url2, format: new ol.format.KML({ showPointNames: false }) }) });
// -------------------------------------------------------------------
// 中央にセンターマーカー
    app.addCenterMarker = function(opt_options) {
        var options = opt_options || {};
        var anchor = document.createElement('a');
        var 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);
// -------------------------------------------------------------------
// 左上にタイトル
    app.addTitleFig = function(opt_options) {
        var options = opt_options || {};
        var anchor = document.createElement('a');
        var element = document.createElement('div');
        element.className = 'title-fig ol-unselectable';
        element.id = 'title-fig';
        element.appendChild(anchor);
        ol.control.Control.call(this, { element: element, target: options.target });
    };
    ol.inherits(app.addTitleFig, ol.control.Control);
// -------------------------------------------------------------------
// 右下に凡例
    app.addSymbols = function(opt_options) {
        var options = opt_options || {};
        var anchor = document.createElement('a');
        var element = document.createElement('div');
        element.className = 'symbols ol-unselectable';
        element.id = 'symbols';
        element.appendChild(anchor);
        ol.control.Control.call(this, { element: element, target: options.target });
    };
    ol.inherits(app.addSymbols, ol.control.Control);
// -------------------------------------------------------------------
// 地図をクリックした際に,停留点の情報を表示するための overlay 変数(popup 用)
    var overlay = new ol.Overlay({ element: container });
// -------------------------------------------------------------------
// 地図変数 (map 変数) の定義。地理院地図を表示するように指定している
    map = new ol.Map({
        target: document.getElementById('map_canvas'),
        loadTilesWhileAnimating: true,
        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('
'+features[0].get('name')+features[0].get('description')+'
'); 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; }; map.addControl(new ol.control.ZoomSlider()); map.addControl(new app.addCenterMarker()); map.addControl(new app.addTitleFig()); map.addControl(new app.addSymbols()); view.setCenter(ol.proj.transform([center_lon, center_lat], "EPSG:4326", "EPSG:3857")); view.setZoom(initZoom); document.getElementById('opacity_control').innerHTML = initOpacity.toFixed(1); // ------------------------------------------------------------------- // kml_vector に対する処理は,以下のようにイベント処理に入れないと,データが読まれずにうまくいかない。 kml_vector.once('change',function() { kml_extent = kml_vector.getSource().getExtent(); view.fit(kml_extent, map.getSize()); // for animation;以下は kml_vector.once('change',function() に入れないと変数に値が入らなかった。 var features = kml_vector.getSource().getFeatures(); // 通常の JavaScript の array for (var j=0; j < features.length; j++) { var coordArray = features[j].getGeometry().getCoordinates(); // geometry 変数の中の座標を配列へ(メルカトル座標系) if (j == 0) { routeCoords = coordArray; } else { routeCoords = routeCoords.concat(coordArray); } // 全ルートの座標配列を結合 } routeLength = routeCoords.length; geoMarker = new ol.Feature({ type: 'geoMarker', geometry: new ol.geom.Point(routeCoords[0]) }); styles = { 'geoMarker': new ol.style.Style({ image: new ol.style.Circle({ radius: 7, snapToPixel: false, fill: new ol.style.Fill({color: 'black'}), stroke: new ol.style.Stroke({ color: 'white', width: 2 }) }) }) }; vectorLayer = new ol.layer.Vector({ source: new ol.source.Vector({ features: [geoMarker] }), style: function(feature) { // hide geoMarker if animation is active if (animating && feature.get('type') === 'geoMarker') { return null; } return styles[feature.get('type')]; } }); map.addLayer(vectorLayer); }); // kml_vector.once('change',function() // ------------------------------------------------------------------- moveFeature = function(event) { var vectorContext = event.vectorContext; var frameState = event.frameState; if (animating) { var elapsedTime = frameState.time - now; // here the trick to increase speed is to jump some indexes on lineString coordinates var index = Math.round(speed * elapsedTime / 1000); if (index >= routeLength) { stopAnimation(true); return; } var currentPoint = new ol.geom.Point(routeCoords[index]); var feature = new ol.Feature(currentPoint); vectorContext.drawFeature(feature, styles.geoMarker); } map.render(); // tell OL3 to continue the postcompose animation }; // ------------------------------------------------------------------- startButton.addEventListener('click', startAnimation, false); } // 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); } function centerMarker_visible() { document.getElementById('centerMarker').style.display = "block"; } function centerMarker_invisible() { document.getElementById('centerMarker').style.display = "none"; } function titleSymbol_visible() { document.getElementById('title-fig').style.display = "block"; document.getElementById('symbols').style.display = "block"; } function titleSymbol_invisible() { document.getElementById('title-fig').style.display = "none"; document.getElementById('symbols').style.display = "none"; } // ******************************************************************* // ヒュベニの公式を使った距離計算 // 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 wgs84Sphere = new ol.Sphere(6378137); // measure var measureLength = 0; // for measure var lineLength = 0; var features = kml_vector.getSource().getFeatures(); // 通常の JavaScript の array for (var j=0; j < features.length; j++) { var coordArray = features[j].getGeometry().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]); } for (var i=0; i < (coordArray.length-1); i++) { measureLength = measureLength + wgs84Sphere.haversineDistance(coordArray[i], coordArray[i+1]); } // measure } var outstr; // measure outstr = (Math.floor(measureLength)/1000)+" [km]"; // measure document.getElementById("outStr").innerHTML = " L = "+(Math.floor(lineLength)/1000)+" [km]"+" (ol.measure "+outstr+")"; // measure } // ******************************************************************* function setCenterZoom() { view.fit(kml_extent, map.getSize()); } // ******************************************************************* function startAnimation() { // ボタンはトグルになっているので,if (animating) が必要になる。 if (animating) { stopAnimation(false); } else { animating = true; now = new Date().getTime(); speed = speedInput.value; startButton.textContent = 'Cancel Animation'; geoMarker.setStyle(null); // hide geoMarker view.fit(kml_extent, map.getSize()); map.on('postcompose', moveFeature); map.render(); } } // ******************************************************************* function stopAnimation(ended) { animating = false; startButton.textContent = 'Start Animation'; // if animation cancelled set the marker at the beginning var coord = ended ? routeCoords[routeLength - 1] : routeCoords[0]; /** @type {ol.geom.Point} */ (geoMarker.getGeometry()).setCoordinates(coord); //remove listener map.un('postcompose', moveFeature); } // *******************************************************************

 まず,最初の方の global 変数の定義を見て欲しい。 そこでは,styles,animating,speed, などの変数が null(中身なし)で定義してある。 ここで定義することで,どの関数の中でもその値を使って処理することができる。 意味としては,
  styles:動いていく黒丸のためのスタイルの定義を入れておく変数
  animating:アニメーションが実行中かどうかを示す boolean 変数
  speed:アニメーションを動かす速度を入れておく変数。1000 が最大値
  now:アニメーション開始時の時刻(1970/01/01 00:00:00 からの秒数)
  speedInput:スピード指定のスライドバー要素を示す変数(document.getElementById() 関数で指定)
  startButton:アニメーション開始ボタン要素を示す変数(document.getElementById() 関数で指定)
  routeCoords:全経路の経路点を入れた配列。各経路点を表す要素は経度と緯度のペア(配列)となっている。
  routeLength:全経路の長さ(点数)
  geoMarker:移動する黒丸を示す
  vectorLayer:アニメーション用に定義された ol.layer.Vector クラスのインスタンス。ここでは黒丸しか入っていない(もとのサンプルでは,経路や始終点のアイコンも入っていた)
  moveFeature:黒丸という要素(feature)を動かす関数
となっている。

 これらの初期化は基本的に init_map() の中で行われている。 「function init_map()」の中のはじめの方にも青い部分があり,animating 変数と speedInput,startButton の各変数に値を入れている。 初期状態なので animating は false であり,他の2つは id で指定された web 要素を指定している。

 中段ぐらいにある「ol.Map クラスのインスタンス map」の定義では,
loadTilesWhileAnimating: true,
が追加されている。どうもこれがないとうまくアニメーションが動かないんだと思っている。 map の定義としては,この点以外は内容に変更はない。

 さらに中盤の少し下ぐらいにイベント処理を使った KML vector の範囲読み取りなどの処理が書かれている。 そこは赤い文字で書かれた部分から下の方に1行だけある赤い部分まである。 なぜこのような処理が必要か,というと,こうしないと KML のデータ情報をうまく読み出せなかったからである。
 KML のデータは,init_map() の最初の方で URL などの情報を使って定義されている。 しかし,定義されただけでは実際にはすぐには読み込まれないみたいであり, 定義の直後に KML データの経路点の座標を読み出そうと思っても「未定義」というエラーが出てしまう。 そこで,map の定義の後にやってみたが,それでも経路点の座標は読み出せなかった。 そのため,このイベント処理が必要になる。 ここでのイベント処理は,KML に変更があれば一度だけおこないなさい,という処理であり,KML のデータが読み込まれることで KML に変更が加わったとして発動するみたいである。

 イベント処理の最初(赤い部分)では,イベント処理の定義部分と自動で表示範囲を調整する処理が書かれている。 その下の青い部分がアニメーションのために追加された部分である。 最初に,KML vector から,全ルート点の座標を一つの配列に入れている。 ルートは複数のルートがある可能性があるので,少しややこしい書き方になっている。 次に,geoMarker として黒丸を定義している。 また,geoMarker が定義されている ol.layer.Vector クラスのインスタンスを定義している。 そして,その ol.layer.Vector クラスのインスタンスを map に加えて (map.addLayer()) いる。
 次に,黒丸を動かす moveFeature という関数の定義が書かれている。
if (animating)
の中で,スピードに合わせて点の位置を進めて,その点に黒丸を打つ,という作業をしているみたい(ちゃんとはわかっていない…)。 最後に map.render() として描画させている。
 そして,その下でスタートボタンに対して,addEventListener として,イベント処理の待ちループを起動している。

 JavaScript の最後の部分に startAnimation と stopAnimation という関数が定義されている。 startAnimation は,アニメーションが起動している時(ボタンの表示が Cancel Animation となっている)は,アニメーションを止めるために,stopAnimation を呼び出している。 アニメーションが動いていない時(ボタン表示が Start Animation となっている)は,アニメーションを動かすために,スタート時刻 now を取得し,speed 値を取得し, スタートボタンに Cancel Animation と表示させ,地図の表示範囲を経路がちょうど入るように fit させてから,イベント処理で moveFeature が呼び出されるように指定している。
 stopAnimation では,アニメーションを止めるために,animating 変数を false にし,スタートボタンの表示を Start Animation に戻し,黒丸 (geoMarker) を始点に表示して, moveFeature を呼び出すイベント処理をキャンセルしている。

 以上の処理によって,スタートボタンを押すと,黒丸が経路に沿って動いていく。 動く速度は,スタートボタンをおした時のスピード指定スライドバーの値で決まる。 黒丸が動き出してからスピードを変えようと思っても変更はされない。

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

その19:その9からその18までのまとめに続く

0 件のコメント: