2014年9月10日水曜日

OpenLayers 3 を使ってみよう (その4:マウスクリックで地図上に経路を描画する)

これはOpenLayers 3 を使ってみよう(その3:地図上に折線を引く)からの続きである。
OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。
ここでは OpenLayers 3.7.0 を使っている。
 前回(その3:地図上に折線を引く)では, 地図上に折線を描画する方法を記述した。
今回はマウスのクリックによって折線を次々と描画して,経路を表示させようと思う。

 方法としては,マウスをクリックした際のイベント処理で,折線用の点配列に点の座標を加え,折線を再描画させる。 また,最後に加えた点を削除するボタンと,すべてのデータを消すボタンを用意して,経路の訂正や作り直しができるようにする。

 まず,いつものように以下に 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; }
</style>
<title>OpenLayers 3 Example: Draw a Line by Mouse Click</title>
<script src="ol3ex4.js" type="text/javascript"></script>
</head>

<body onload="init_map()">
  <div id="map_canvas" style="float:left; width:76%; height:100%;"></div>
  <div id="control_panel" style="float:right;width:24%;text-align:left;padding-top:10px;font-size:85%">
    <div style="font-size:100%">
      &nbsp;不透明度:<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><br>
      &nbsp;<b>不透明度 Δ=±0.2:
      <a title="decrease opacity" href="javascript: changeOpacity(-0.2);">&lt;&lt;</a>
      <span id="opacity_control"></span>
      <a title="increase opacity" href="javascript: changeOpacity(0.2);">&gt;&gt;</a></b>
      <br>
      <button id="clearAllPoints" onclick="clearAllPoints();">Clear All Pts</button><br>
      <br>
     <button id="deleteLastPoint" onclick="removeLastPoint();">Delete Last Point</button>
      &nbsp;<span id="outStr"></span><br>
     <textarea cols="46" rows="45" id="latlng_display" style="font-size:7.5pt;"></textarea><br>
    </div>
  </div>

</body>
</html>
 今回の web ページ上の変更点は,「全ポイント削除」と「最後の点の削除」のボタンを用意したのと,点のデータをテキストで出力する領域(<textarea>)を用意した点である。逆に「折線を描画する」ボタンは削除してある。

 次に JavaScript を載せよう。ここでも変更箇所の色を変え,説明は web ページのソースと同じくこの下に書いておく。
// ===================================================================
var map = null;      // 全体の地図用の変数
var cyberJ = null;   // 地理院地図用の変数

var lineVector = null;                             // vector layer variable
var vectorSource = null;                           // variable for source
var vectorFeature = null;                          // variable for feature
var lineStrings = new ol.geom.MultiLineString([]); // line instance of the path
var lineStrArray = new Array();                    // line data array as lineString format [[pt0,pt1],[pt1,pt2],[pt2,pt3],....]
var coordArray = new Array();                      // line data array [pt0, pt1, pt2,...], eath point is pt0=[lon0,lat0]
var coord = new Array();                           // クリックした点の座標を渡す変数
// -------------------------------------------------------------------
var lineColor = '#ff0000'; // red

var center_lon = 135.100303888; // 中心の経度(須磨浦公園)
var center_lat = 34.637674639; // 中心の緯度(須磨浦公園)

var initZoom = 10; // ズームの初期値
var MinZoom  = 6;   // ズームの最小値(最も広い範囲)
var MaxZoom  = 17;  // ズームの最大値(最も狭い範囲)
var initPrecision = 8; // 座標表示の小数点以下の桁数の初期値

var initOpacity = 1.0; // 不透明度の初期値
var gMaxOpacity = 1.0; // 不透明度の最大値
var gMinOpacity = 0.0; // 不透明度の最小値
// *******************************************************************
function init_map() {
// 表示用の view 変数の定義。
    var view = new ol.View({ projection: "EPSG:3857",
        maxZoom: MaxZoom,
        minZoom: MinZoom
   })

// 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"
        })
    })

// 地図変数 (map 変数) の定義。地理院地図を表示するように指定している。
    map = new ol.Map({
        target: document.getElementById('map_canvas'),
        layers: [cyberJ],
        view: view,
        renderer: ['canvas', 'dom'],
        controls: ol.control.defaults().extend([new ol.control.ScaleLine()]),
        interactions: ol.interaction.defaults()
    });


// 地図をクリックしたら点を追加し線を再描画させる。小数点以下の桁数は initPrecision で指定。メルカトル座標 (EPSG:3857) を WGS84 (EPSG:4326) に変換している。
    map.on('click', function(evt) {
        var coordinate = evt.coordinate;
        var stringifyFunc = ol.coordinate.createStringXY(initPrecision);
        coord = ol.proj.transform(coordinate, "EPSG:3857", "EPSG:4326");
        addPoint(coord);
    });

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

} // function init_map()
// ===================================================================
// 地理院地図 (var cyberJ) の opacity(不透明度) を変える
// DOM の指定で,document.getElementById('opacity_control').innerHTML とすると,うまくいかず。jQuery と干渉してのかな?
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 writeData() {
    var outstr = '';
    document.getElementById("latlng_display").value = ''; // 一度文字表示をクリア
    for (i=0; i< coordArray.length; i++) { outstr = outstr+coordArray[i].toString()+"\n"; }
    document.getElementById("latlng_display").value = outstr; // 文字列を表示し直す
}
// ===================================================================
// 点データの再表示とラインの再描画サブルーチン
// 再描画は2点以上ないと行わない。
function drawLine_sub() {
    document.getElementById("outStr").innerHTML = coordArray.length+" pts";
    writeData(); // 文字列データを表示しておく

// 点が2点以上あれば,経路線用の vector データを作る(作り直す)
    if (coordArray.length > 1) {
        lineStrArray.length = 0;  // 経路線用の配列を一度クリア(これをしないと変に画像が残る)
        for (i=0; i<(coordArray.length-1); i++) { lineStrArray.push([coordArray[i], coordArray[i+1]]); }
        lineStrings.setCoordinates(lineStrArray);
        vectorFeature = new ol.Feature(lineStrings.transform('EPSG:4326', 'EPSG:3857'));
    }
}
// -------------------------------------------------------------------
// ラインの初期描画サブルーチン
// coordArray の長さが 2 以上でないと呼び出してはいけない(線が描けないため)
function init_drawLine_sub() {
// 経路線用の vector データを作る
    drawLine_sub();
    vectorSource  = new ol.source.Vector({ features: [vectorFeature] }); // vector layer 用のソースの作成
// 経路用の vector layer の作成
    lineVector = new ol.layer.Vector({
        source: vectorSource,
        style: new ol.style.Style({
            stroke: new ol.style.Stroke({ color: lineColor, width: 2 })
        })
    });
    // vector layer の追加
    map.addLayer(lineVector);
}
// ===================================================================
// 点の追加ルーチン
// vectorFeature を書き換えると,自動で画像が追加される。coordArray.push([136.1720, 34.2250]);
function addPoint(coord) {
    coordArray.push(coord); // 配列に追加
// 経路点配列が2になれば,vector layer を新たに作る。それ以外は vectorFeature を更新すれば再描画される
    if (coordArray.length == 2) { init_drawLine_sub(); } else { drawLine_sub(); }
}
// -------------------------------------------------------------------
// 最後の点の削除ルーチン
function removeLastPoint() {
// 点が1点以上あれば,点の削除処理を行う
    if ( coordArray.length > 0) {
        coordArray.pop(); // 配列の最後の点を削除
// 点が1点に減れば,vector layer を消す
        if (coordArray.length == 1) { map.removeLayer(lineVector); }
        drawLine_sub();
    }
}
// -------------------------------------------------------------------
// 全ての点を削除するルーチン
function clearAllPoints() {
    coordArray.length = 0;    // 最初に経路点用の配列をクリア
    lineStrArray.length = 0;  // 経路線用の配列もクリア(これをしないと変に画像が残る)
    document.getElementById("latlng_display").value = ''; // 文字データもクリア
    document.getElementById("outStr").innerHTML = "0 pts"; // 点数もゼロにセット
    map.removeLayer(lineVector); // vector layer も消しておく
}
// *******************************************************************
 今回は JavaScript の部分はかなり書き直されている。 特に描画関係の部分をサブルーチン化するなどしているため,前回とはかなり違ったものになっている。 以下に変更点について説明しよう。

 (1) 変数定義の所で,描画関係の多くの変数をグローバルな変数として定義している。
   これはサブルーチンの中などで使うための処置である。
   また,coordArray という配列を定義しているが,これは経路の「点」の配列である。
   この配列から,折線用の配列 lineStrArray にデータを入力している。
   経路に点を加えたり,あるいは一点削除する際には,coordArray に対して追加や削除の処理を行い,
   coordArray から経路を描画しなおしている。

 (2) マウスクリックした際のイベント処理( map.on の中身)の後半が書き換わっている。
   今回はマウスがクリックされると,座標データを持って addPoint() ルーチンを呼び出している。

 (3) 青字で書かれた writeData() ルーチンは,coordArray が持っている点のデータをテキストで書き出すルーチンである。
   ここでは document.getElementById("xxxx").innerHTML ではなく,document.getElementById("xxxx").value を使っている。
   これは,出力先が <textarea> であり,元々ユーザーが入力するための領域だからである。
   この場合は innerHTML ではなく,value を使わないといけない。

 (4) 緑で書かれた drawLine_sub() ルーチンオレンジで書かれた init_drawLine_sub() ルーチンだが,
   これらは後で出てくる「点の追加」,「最後の点の削除」,「全データの削除」ルーチンで呼び出されるサブルーチンである。
   drawLine_sub()init_drawLine_sub() の中でも呼び出されている。
   形の上では,init_drawLine_sub()(と drawLine_sub())が線の描画ルーチンを形成している。
   drawLine_sub() を独立したサブルーチンとしているのは,drawLine_sub() を呼び出すだけで再描画できるだからである。
   従って,最初に線を描画する際に init_drawLine_sub() を呼び出し,それ以降の再描画の際には drawLine_sub() を呼び出す。

 (5) 紫で書かれた addPoint()赤字で書かれた removeLastPoint()青字で書かれた clearAllPoints()は,
   それぞれ,「点の追加」,「最後の点の削除」,「全データの削除」ルーチンである。

   addPoint() では,coordArray に点を追加し,線を描画(または,再描画)している。
   経路点が初めて2点になった時点で,線を描くために init_drawLine_sub() を呼び出している。
   その他の場合は drawLine_sub() が呼ばれるが,1点のみの時は drawLine_sub() の中で無視される。

   removeLastPoint() では,1点以上あれば coordArray から最後のデータを削除(pop)し,再描画している。
   その結果,1点のみになった時は,lineVector(vector layer タイプの変数)を map から取り除いている。
   点の数にかかわらず drawLine_sub() を呼び出しているが,2点以上なければ無視される。

   clearAllPoints() では,点配列,折線配列,文字領域をクリアし,vector layer も削除している。
   これらの処理によって,全ての描画点が抹消される。

 これで,マウスのクリックで経路を作ることができる。 テキスト領域には,経度,緯度の順で座標が書かれている。 この文字データをコピーして KML ファイルを作れば,簡単に経路データが作れる。 地図を見ながら経路を作れて大変便利である。 次回は文字データの情報から逆に経路線を描画できるようにしたい。 それができると,手動での経路作成が大変楽に行えるようになる。

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

その5:テキストデータから折線データ読込みに続く

0 件のコメント: