2014年9月11日木曜日

OpenLayers 3 を使ってみよう(その6:ol.interaction.Draw を使った例)

これはOpenLayers 3 を使ってみよう(その5:テキストデータから折線データ読込み)からの続きである。
OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。
ここでは OpenLayers 3.7.0 を使っている。
 前回(その5:テキストデータから折線データ読込み)では, テキストデータ形式で与えた経路点の座標情報から,経路を描画する方法を記述した。 そこでは,経路の距離を計算し,また,経路ができるだけ大きく表示されるような工夫も施した。
今回は,ol.interaction.Draw というマウスによる経路作成の別の方法についてみてみようと思う。

 マウスによる経路の作成は,その4:マウスクリックで地図上に経路を描画するその5:テキストデータから折線データ読込みでも紹介したが,その時は座標点を入れる配列を用意し,その座標配列から ol.geom.MultiLineString クラスの要素を持つ vector layer を作って描画させていた。 今回は,ol.interaction.Draw というものを使って経路線を作成したいと思う。

 今回使う ol.interaction.Draw というのは,ol.Interaction(地図を構成する要素に変更を加えるもの)の一種であり,マウスクリックで線を作成するのに便利なものである。 具体的な例が OpenLayers 3 の Examples のページの中の, Draw features example (draw-features.html)Draw and modify features example (draw-and-modify-features.html) にある。 この2つはいずれも ol.interaction.Draw を含む使用例であるが,Draw features example (draw-features.html) は作成した線等は変更できないが,Point, LineString, Polygon の他に Circle, Square, Box というのが選べる。 Draw and modify features example (draw-and-modify-features.html) の方は,ol.interaction.Modify も使われており,Point, LineString, Polygon のみだが,作成した線等の点や線分を地図上で移動(変形)させることができる。

 今回は ol.interaction.Draw のみを試してみよう。 ポイントは,ol.layer.Vector クラスの変数を用意し,その構成要素である source に対して,ol.interaction.Draw というタイプの動作(関数と言うべきか?)を割り当てる。 つまり ol.layer.Vector クラスの変数の構成要素 source(の中の feature)に対して線を描く,という感じであろうか。

 さっそく,いつものように web ページのソースを載せよう。 今回は色の付け方がこれまでと違っている。 web ページのソース部分は,赤色部分が JavaScript 関連の部分であり,灰色部分は web の基本的な要素である。 オレンジ色の部分は JavaScript の出力(表示)関連であり,緑色部分は不透明度変更に関連した部分となっている。 青はタイトルで,その他が黒色となっている。 ここのあるのはこれまでに出てきたものばかりなので,説明は割愛したい。
<!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; }

   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: Draw Interaction</title>
<script src="ol3ex6.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%">
 (1) クリックの度に点が増え,ダブルクリックで終端<br>
 (2) 複数の線を入力可能<br>
  <hr size="1" color="#808080">
    <div style="font-size:100%">
      &nbsp;<b>不透明度 Δ=±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="getGeometry" onclick="getGeometryFromFeature();" class="boldblack">Display Line Data</button><br>
      &nbsp;<span id="outStr" style="font-size:9pt;"></span>
      &nbsp;<span id="outStr3" style="font-size:9pt;"></span>
      <br>
     <textarea cols="46" rows="45" id="latlng_display" style="font-size:7.5pt;"></textarea><br>
      &nbsp;<span id="outStr2" style="font-size:9pt;"></span><br>
    </div>
  </div>
</body>
</html>

 次に JavaScript を載せよう。 ここでも色の付け方を変えてみた。 灰色部分はグローバルな変数の定義であり, 赤色部分が OpenLayers 3 の基本的な部分(これまでに出てきている)である。 青色部分が今回特に重要な部分であり,その他のオレンジ色の部分は JavaScript の出力(表示)関連, 緑色部分は不透明度変更に関連した部分となっている。 その他の部分は黒色となっている。 項目事の説明は下に書こう。
// ===================================================================
var map = null;      // 全体の地図用の変数
var view = null;     // 地図の表示用変数
var cyberJ = null;   // 地理院地図用の変数

var source = null;
// -------------------------------------------------------------------
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() {
// テキストエリアのクリア
    document.getElementById("latlng_display").value = '';

// 表示用の view 変数の定義
    view = new ol.View({ projection: "EPSG:3857", 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"
        })
    })

    source = new ol.source.Vector();
    var vector = new ol.layer.Vector({
        source: source,
        style: new ol.style.Style({
            stroke: new ol.style.Stroke({ color: '#ff0000', width: 2 })
        })
    });

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

    map.addControl(new ol.control.ZoomSlider());
    view.setCenter(ol.proj.transform([center_lon, center_lat], "EPSG:4326", "EPSG:3857"));
    view.setZoom(initZoom);

// ol.interaction.Draw の追加
    var draw = new ol.interaction.Draw({
        source: source,
        type: 'LineString'
    });
    map.addInteraction(draw);
// -------------------------------------------------------------------
// span opacity_control (地理院地図の不透明度) に初期値(実数)を入れる。
    document.getElementById('opacity_control').innerHTML = initOpacity.toFixed(1);
} // function 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);
}
// ===================================================================
// 文字データの表示ルーチン:メルカトル座標系から WGS84 へ変換してテキスト表示している。
function writeData(coordArray) {
    var outstr = document.getElementById("latlng_display").value; // 既存の文字列を取得
    for (var i=0; i< coordArray.length; i++) { outstr = outstr+ol.proj.transform(coordArray[i],"EPSG:3857", "EPSG:4326").toString()+"\n"; }
    outstr = outstr + "\n"; // 1行空行を入れておく
    document.getElementById("latlng_display").value = outstr; // 文字列を表示し直す
}
// *******************************************************************
function getGeometryFromFeature() {
    var features = source.getFeatures(); // array of features
    var lengf = features.length; // length of the features array

    var geometryName = features[0].getGeometryName(); // 'geometry'
    var geometryType = features[0].getGeometry().getType(); // 'LineString' etc

    document.getElementById("latlng_display").value = ''; // 文字列のクリア
    for (var j=0; j<lengf; j++) {
        var geometry = features[j].getGeometry();
        var coordArray = geometry.getCoordinates();
        writeData(coordArray);
    }
    document.getElementById("outStr2").innerHTML = " &lt;&lt;"+lengf+" lines&gt;&gt;<br>";
    document.getElementById("outStr2").innerHTML = document.getElementById("outStr2").innerHTML + " geometry 領域名:'"+geometryName+"'<br>";
    document.getElementById("outStr2").innerHTML = document.getElementById("outStr2").innerHTML + " geometry タイプ:'"+geometryType+"'";
}
// *******************************************************************
 まず,赤色部分で書かれている OpenLayers 3 の基本的な部分だが,これまでにも書いているので,細かい点は割愛したい。 わからない時は,OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)にある目次から該当するものを探して説明を見てみて欲しい。 おおまかには,地図として地理院地図を用いており,'map_canvas' という id を持つ web 要素に地図が描かれる。 controls(スイッチみたいなもの)や interactions(地図や地図上の物体へのなんらかのアクション(動作))もとりあえず基本的な物がデフォルトで設定されている。

 青色部分が,今回の肝心な部分なのだが,上から見ていくと,まずグローバルな変数として source が定義されている。 そして init_map() の中で,中身が空っぽの「source」を持つ ol.source.Vector クラスの変数(インスタンス)が設定されている。 これは変数を用意しておくが,変数に値を入れるのは別の所(今回は ol.interaction.Draw がやっている)となっている。 その次の行で「var vector」を定義している。 これはマウスで作図する経路線(ol.geom.LineString)が入る変数であり,その中でも構成要素である source(直上で定義された)に線が入れられる。 ここでは,経路線データを入れる場所(source)と,線などの諸性質(style)を定義している。
 これら以外では,下の方で「var draw」を定義して,map.addInteraction(draw); として map に追加している。 これはここで定義した source(経路線データの入れ物)に対する動作として,ol.interaction.Draw クラスの動作(具体的には線を描く)を map に加えている。 ここでは draw という変数を定義しているが,直接 map.addInteraction() で draw の中身を書いても構わない。

 緑色部分は不透明度変更関連であり,ここでは説明は省略したい。 説明はその2:地図の不透明度を変えるを見て欲しい。

 オレンジ色の部分は,作図した経路線のデータを出力する部分である。 上から見ていくと,まず「writeData」というのがある。 これは,通常の配列に入れられた座標データを,文字列として出力する関数である。 データが球面メルカトル座標系(EPSG:3857)で入っているので,WGS84(EPSG:4326)に変換してから文字列として出力している。

 「getGeometryFromFeature()」関数では,作図された経路線の変数から,経路点の情報を取り出して,上記の writeData 関数に送っている。 この部分は今回かなり苦労して理解した部分である。 少し詳しく見ていこう。 まず,作図された経路線の情報は ol.layer.Vector クラスの中で定義された「source」という変数に入ってる。 しかし,この source という変数に何が入っているのかよくわからなかった。 実は,この source の中には線の情報である feature という要素が入っている(他にも入っているが…)。 feature というのは「特徴(の情報)」なのだが,ここでは「図形を特徴付ける情報を保持したもの」みたいな意味で使われているみたいな気がしている。

 今回の場合,source の中には複数の feature が含まれる(複数の図形が含まれる)可能性がある。 そのため,feature を取り出すと,それは配列として取り出される。 そして,各 feature(各図形)から,図形の具体的な要素(今回の場合は 点の配列)を取り出す。 「var geometry = features[j].getGeometry();」の行がその作業を行っている。 ここでは「geometry」と呼ばれているが,この geometry が具体的に図形を表す変数(ol.geom.Geometry クラス)である。 今回は経路線(折れ線)なので,この geometry は「ol.geom.LineString」というタイプのものである。 つまり,今回は「geometry」の中には,この LineString という形式のデータが入ることになる。

 そして,ol.geom.LineString タイプの変数に「getCoordinate()」という関数を作用させると,「点の配列」(coordArray)が得られる。 この coordArray は,経度と緯度を要素に持つ「座標(ol.Coordinate)」タイプのデータの配列であり, 通常の数値の配列(2次元配列になっている)なので,表示するのは普通の JavaScript で配列を表示すればいいことになる。 今回は複数の feature(図形)の全てに対して,上記の操作を行い,点の配列をテキストとして表示させている。

 他にも,最初の feature(feature[0])が持つ geometry を表すオプション名(デフォルトは geometry)や, feature[0] の geometry のタイプ(ここでは 'LineString')を表示させている。

 今回は,ol.layer.Vector を用いた ol.interaction.Draw の例を使ってみた。 しかし,この例では,一度作った経路線(図形)を変形させられない。 消してから描き直せば直せるが,できれば折れ線を引っ張ったり伸ばしたりして,変形させられるととても便利である。 そこで,次回は ol.interaction.Modify を使った例を試してみたい。

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

その7:ol.interaction.Modify を使った修正可能バージョンに続く

0 件のコメント: