2016年5月30日月曜日

OpenLayers 3 を使ってみよう(その14:GPX による経路の表示・各点の通過時刻を知る)

 少し時間が経過しているが,一応 OpenLayers 3 を使ってみよう(その13:センターマークと凡例の表示・KML による経路+マーカーの場合)からの続きになる。 OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。
 これまでのページでは OpenLayers v3.7.0 で書いてきたが,ここでは OpenLayers 3.16.0 で書かれている。
 前回(その13:センターマークと凡例の表示・KML による経路+マーカーの場合)までは,KML 形式の経路データファイルを使って,地図上に経路を表示する例について書いてきた。 今回は GPX 形式の経路データファイルを使った経路の描画について書こうと思う。 GPX ファイルについての説明は,英語だが GPX 1.1 Schema Documentation を見てみて欲しい。

 前回(その13:センターマークと凡例の表示・KML による経路+マーカーの場合)との違いは,主に以下の点である。
  ・OpenLayers 3 のバージョンを v3.7.0 から v3.16.0 とした。
  ・KML 形式を GPX 形式に変更した。
  ・データは,経路データファイルと,経路上の各点の時刻表示行うためのファイルを用意し,アイコンの表示はなしにしている。
  ・アイコンの凡例を表示しないようにした。
  ・前回の追記2の作戦を使って,地図読み直後に表示範囲を GPX データに合わせて自動調整している。

 GPX 形式については,v3.7.0 の時点ではデータファイルに不備があると読み込んでくれなかった。 しかし,(たぶん)v3.11.0 以降は GPX 形式のデータファイルもちゃんと読みこむようになったので,OpenLayers 3 のバージョンを変えている。 また,ここでは,処理の都合上,アイコン表示をしてないので,アイコンの凡例 (symbols) は表示しないようにした。 そのため凡例表示関連の変数などがスクリプトから消されている。

 まず用意した GPX 形式の経路データについて述べよう。
用意したのは六甲全山縦走をした際のデータであり,以下の様なものである。
<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1">
<metadata>
        <name>六甲全山縦走2016春</name>
</metadata>
<trk>
<name>Track 1</name>
<time>2016-04-29T21:24:06Z</time>
<desc><![CDATA[Original tracking filename : LOG00000.nma]]></desc>
<trkseg>
    <trkpt lat="34.63374" lon="135.083273333333">
        <ele>20.5</ele>
        <time>2016-04-29T21:24:06Z</time>
    </trkpt>
......(中略)......
    <trkpt lat="34.8081666666667" lon="135.340691666667">
        <ele>60.0</ele>
        <time>2016-04-30T09:36:41Z</time>
    </trkpt>
</trkseg>
</trk>
</gpx>
 これは,以下で「work/map_data.gpx」として使ってる方のファイルである。 内容は,<gpx> と </gpx> に挟まれたファイルの中に,<trk> と </trk> に挟まれた経路データがある。 経路データは,<trkseg> と </trkseg> に挟まれ,各点には,緯度,経度,高度,時刻の情報があるのみ,というシンプルなものにしてみた。 ちなみに実際に使ってるファイルは改行を減らしてあるので見た目は違って見えると思う。 また,経路点は GPS ロガーのデータを用いているが,Douglas-Peuckerのアルゴリズムを使って間引いている

 もう1個のファイルは,同じ位置情報を持っているが,<trk> ではなく,<wpt> として定義している。 それにより,最初のデータは経路を線で表し,2個目のデータは経路を点で表すことになる。 この2個目のデータは,各点の通過時刻を吹き出しで表示させるために用意した。 きっと1個目のデータファイルのデータ(線のデータ)から情報を表示できると思っているが,クリックした場所に一番近い経路点を探して, その情報を吹き出しに出す,みたいな処理を考えるのが面倒で,別ファイルに点として情報を入れることにした(逃げた?)。
<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1">
<metadata>
        <name>六甲全山縦走2016春</name>
</metadata>
<wpt lat="34.63374" lon="135.083273333333">
    <ele>20.5</ele>
    <time>2016-04-29T21:24:06Z</time>
    <name>pt 2</name>
</wpt>
......(中略)......
<wpt lat="34.8081666666667" lon="135.340691666667">
    <ele>60.0</ele>
    <time>2016-04-30T09:36:41Z</time>
    <name>pt 2</name>
</wpt>
</gpx>
これは,以下で「work/map_wpdata.gpx」として使っている。

 次に,いつものように以下に web ページのソースを載せる。 web ページのソース部分は,赤色部分が JavaScript 関連の部分であり,灰色部分は web の基本的な要素である。 青い部分は,センターマークや凡例と web のタイトルである。 水色の部分はマーカーの吹出し関連であり,緑色部分は不透明度変更に関連した部分, 紫色の部分は JavaScript を呼び出すボタン類であり, オレンジ色の部分は zoom の変更や距離に関連する部分である。 その他が黒色となっている。 また,アンダーラインが入っているものは前回の KML 用のものとの違いがある部分である。 説明はソースコードに下に書こう。
<!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: 240px;
       margin-top: 0px;  margin-left: 0px;
       background-image:url(./o3cjmap_title.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: GPX Layer Point Information</title>
<script src="ol3ex14.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%">
  <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="gpx_vector_visible" onclick="gpx_vector_visible();" class="blue">表示</button>
  <button id="gpx_vector_invisible" onclick="gpx_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>
   タイトル・凡例<button id="titleSymbol_visible" onclick="titleSymbol_visible();" class="blue">表示</button>
  <button id="titleSymbol_invisible" onclick="titleSymbol_invisible();" class="red">非表示</button>

   <button id="autoZoomButton" onclick="setCenterZoom();" class="boldblue">Zoom 自動設定</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 ページ上の変更点は,OpenLayers 3 のバージョンを変更し,web ページのタイトルを変更したぐらいで,他は基本的に変更はない。 変更点や注意点のほとんどは JavaScript の中にあるので,次の JavaScript について見てみよう。

 次に JavaScript を載せよう。 色は,灰色部分が変数や定数の定義である。 赤色部分は距離に関連しており, 青い部分は,センターマークや地図タイトルに関連した部分である。 水色の部分はマーカーの吹出し関連であり,緑色部分は不透明度変更に関連した部分, 紫色の部分は表示・非表示のボタン類であり, オレンジ色の部分は zoom の変更などに関連する部分である。 その他が黒色となっている。 説明は 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 gpx_vector = null; // GPX ファイル用変数 1
var wp_vector = null;  // waypoint GPX ファイル用変数 2
var gpx_extent = null; // GPX ファイルの領域範囲用変数

var center_lon = 135.083273333333; // 表示中心の経度(デフォルトは始点の経度)
var center_lat = 34.63374; // 表示中心の緯度(デフォルトは始点の緯度)

var gpx_url  = "work/map_data.gpx";
var gpx_url2 = "work/map_wpdata.gpx";

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');

    var style = {
        'Point': new ol.style.Style({ image: new ol.style.Circle({
            fill: new ol.style.Fill({ color: 'rgba(255,0,255,0.7)' }), 
            radius: 2.5, 
            stroke: new ol.style.Stroke({ color: '#0000ff', width: 2.5 })
        }) }),
        'LineString': new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#ff0000', width: 3 }) }),
        'MultiLineString': new ol.style.Style({ stroke: new ol.style.Stroke({ color: '#ff0000', width: 3 }) })
    };

// 表示用の 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"
        })
    });
// 経路の GPX データ
    gpx_vector = new ol.layer.Vector({
        source:new ol.source.Vector({ url: gpx_url, format: new ol.format.GPX() }),
        style:function(feature) { return style[feature.getGeometry().getType()];}
    });
// マーカーの GPX データ
    wp_vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: gpx_url2, format: new ol.format.GPX() }),
        style:function(feature) { return style[feature.getGeometry().getType()];}
     });
// -------------------------------------------------------------------
// 中央にセンターマーカー
    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);
// -------------------------------------------------------------------
// 地図をクリックした際に,停留点の情報を表示するための overlay 変数(popup 用)
    var overlay = new ol.Overlay({ element: container });
// 地図変数 (map 変数) の定義。地理院地図を表示するように指定している
    map = new ol.Map({
        target: document.getElementById('map_canvas'),
        layers: [cyberJ, gpx_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) {
            features.push(feature);
        });
        if (features.length > 0) {
            var info = [];
            var date = new Date(features[0].getGeometry().getCoordinates()[3]*1000);
            info.push('<div id="wp_desc" style="font-size:12px; width:215px">"'
                      +features[0].get('name')+"<br> elev.= "+features[0].getGeometry().getCoordinates()[2]+"[m], "
                      +date.toLocaleString()+"</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;
    };

    map.addControl(new ol.control.ZoomSlider());
    map.addControl(new app.addCenterMarker());
    map.addControl(new app.addTitleFig());

    view.setCenter(ol.proj.transform([center_lon, center_lat], "EPSG:4326", "EPSG:3857"));
    view.setZoom(initZoom);
    document.getElementById('opacity_control').innerHTML = initOpacity.toFixed(1);

    gpx_vector.once('change',function() {
        gpx_extent = gpx_vector.getSource().getExtent();
        view.fit(gpx_extent, map.getSize());
    });
} // 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 gpx_vector_visible()   { gpx_vector.setVisible(true); }
function gpx_vector_invisible() { gpx_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"; }
function titleSymbol_invisible()  { document.getElementById('title-fig').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 lineLength = 0;
    var features = gpx_vector.getSource().getFeatures(); // 通常の JavaScript の array
    var lengf = features.length;
    for (var j=0; j < lengf; j++) {
        var coordArray = features[j].getGeometry().getCoordinates()[0]; // 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() {
   view.fit(gpx_extent, map.getSize());
}
// *******************************************************************

 ここでも,web ページのソースファイルと同じように,前回からの変更点にはアンダーラインが引かれている。 説明がなされていない部分は,これまでに説明がなされているために,ここでは説明が割愛されていると考えて欲しい。 わからない時は,OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)にある目次から該当するものを探して説明を見てみて欲しい。

 以下,今回の変更点や注意点について書こう。
まず,灰色の変数定義の部分だが,変更は読み込むファイル名や,変数名を kml_vector から gpx_vector に変えた,などの自明な感じのものがほとんどである。唯一,gpx_extent という GPX 経路データの範囲を表す変数を新しく定義している。

 次に初めの方にある黒い部分だが,そこでは関数 init_map() の定義がなされている。 その中で,GPX 形式の2個のファイルを読み込ませている。 1個目は経路を表示するためのデータ(トラックデータ)であり,もう1個は経路の各点に小丸を表示して,クリックでその点の高度と通過時刻を吹き出しで表示するための点のデータである。 これら2個のデータは本質的に同じ点のデータなので1個にできないことはないような気もするが,今回は別にしている。 (もしかしたら別々が正しいのかも…) GPX 形式の指定は,new ol.format.GPX() として GPX 形式を指定している。 また,点や線のスタイルに関しては,少し上部で var style = ... として,Point, LineString, MultiLineString を一括して定義している。 (これは GPX の example (v3.16.0) にあった例を参考にした。)

 青色部分はセンターマークや地図タイトルなどの表示,非表示に関する部分である。 詳しくは OpenLayers 3 を使ってみよう(その13:センターマークと凡例の表示・KML による経路+マーカーの場合)を見てみて欲しい。 後半にある青い部分に変更点があるが(アンダーラインがある),それはアイコンの凡例表示に関するものを消しただけであり,本質的には何も変わっていない。

 水色部分はマーカーの吹出し関連である。 詳しくはOpenLayers 3 を使ってみよう(その10:KML でマーカーを描画して吹出しをつける)を見てみて欲しいが,ここでの変更点について書いておこう。

 ここでの変更点は,吹き出しの表示内容の変更である。 前回の例では,アイコンをクリックすると吹き出しが表示され,地点名とその点で撮られた写真のページへのリンクを表示していた。 しかし,今回は,経路点(トラックデータ)の全てに丸を表示し,その点の名前(ここでは点の番号)と高度,通過時刻を表示させている。
 読み込んだ GPX 形式の経路データから高度と時刻を取り出すには,それぞれ,
features[0].getGeometry().getCoordinates()[2] <-- [m]単位の高度
date = new Date(features[0].getGeometry().getCoordinates()[3]*1000); 
   <-- ミリ秒単位の時刻データ。toLocaleString() で「2016/4/30 6:36:10」形式へ
としている。
 ここでは各点(クリックされた1点)の情報を取り出している。 そのため,図形は1個のみ(1点のみ)であり,その情報を含む features は features[0] のみ指定している。 その図形 (features[0]) に対して,図形の情報を取得し (getGeometry()),その座標情報を読み取っている (getCoordinates())。

 座標情報は,最初の2個が位置を表す経度 (getCoordinates()[0]) と緯度 (getCoordinates[1]) である。
3番めがメートル単位の高度であり (getCoordinates[2]),4個目が1970年1月1日午前0時を始点とする,秒単位の時刻情報である (getCoordinates[3])。
時刻に関しては,Date 関数の引数としてはミリ秒単位にしたものが必要なので,1000倍して代入し, 最終的に date.toLocaleString() を用いて「2016/4/30 6:36:10」形式の日付+時刻として表示させている。

 緑色部分は不透明度の変更に関連である。 OpenLayers 3 を使ってみよう(その2:地図の不透明度を変える)に記載があるので見てみて欲しい。

 紫色部分は表示・非表示の切替に関する部分である。3回前に記載があるが,あまり難しくはないので,説明は省略しよう。

 次に赤色部分はヒュベニの公式を使って,経路の長さを求めている部分である。 このスクリプトの最初の方に定数を定義していた。後半部分では,実際の距離の計算を行っている。 詳しくは OpenLayers 3 を使ってみよう(その8:ol.interaction.Draw 修正可能バージョンで表示領域の大きさの調整を行う)を見てみて欲しいが,ここでは注意点について書いておこう。

 function length_line() の中を見ると,
var features = gpx_vector.getSource().getFeatures(); // 通常の JavaScript の array
として,図形情報を配列 features に入れている。 ここで言う図形は線 (LineString,トラックデータ) であり,features が配列なのは複数の線が含まれる可能性があるからである。

 そして for (var j=0; j < lengf; j++) { として,各線(各図形)に対して距離の計算を行っている。 距離の計算では,1本の線上の各点の座標情報を coordArray 配列に入れている。
var coordArray = features[j].getGeometry().getCoordinates()[0]; // geometry 変数の中の座標を配列へ(メルカトル座標系)
coordArray という変数が配列になるのは,線の中に複数の点が含まれているからである。

 ここで1点だけ不思議なのは「getCoordinates()[0]」の部分である。 何が不思議かというと,最後に「[0]」が付いている。 これは得られたデータが配列であり,その最初のデータを取ってくることを意味している。 しかし,その最初のデータ自体が線上の全ての点の座標情報の配列となっている。 わかりにくいが,上記の coordArray 変数の定義の最後の「[0]」が無いように定義する方がわかりやすいのだが,何故かこの「[0]」が必要だった。 KML 形式の時にはいらなかったのに…。この点が不思議でならない。

 coordArray という配列に入れた後は,
coordArray[i] = ol.proj.transform(coordArray[i],"EPSG:3857", "EPSG:4326");
として,座標系をメルカトル座標系 ("EPSG:3857") から WGS84("EPSG:4326")と呼ばれる通常の経度,緯度へと変換している。 その上で直前に定義した dist_2pts() 関数で距離を計算して合算している。

 最後に,オレンジ色部分が地図表示の自動調整に関連した部分である。 ここでは getExtent() 関数と fit() 関数を用いてシンプルに2行で範囲を自動調整している。 詳しくは OpenLayers 3 を使ってみよう(その13:センターマークと凡例の表示・KML による経路+マーカーの場合)の最後に追記として記述があるので見てみて欲しいが,ポイントとしては,init_map() ルーチンの最後に,イベント処理という形で gpx_extent(GPX 経路データの範囲)に値を代入して,fit() 関数で表示範囲を自動調節している点である。 また,データ範囲がすでに変数に入っているので,最後の方の setCenterZoom() 関数の中は1行だけになっている。

 「その14」のサンプルを具体的な web ページとして用意したので,具体的な表示を見てみて欲しい。 サンプルを表示させると,青い太い線が見えるが,地図をぐんぐん拡大していくと,青い太い線に見えたのは,経路点に描いた青い丸というのがわかると思う。 その青い丸にポインターを持って行ってクリックすると,その点の高度や座標を表示します。 (ちなみにサンプルページはアクセスログを取るルーチンを組み込んでいます)

その15:OpenLayers v3 で Google Map を表示に続く

0 件のコメント: