2014年9月9日火曜日

OpenLayers 3 を使ってみよう (その3:地図上に折線を引く)

これはOpenLayers 3 を使ってみよう(その2:地図の不透明度を変える)からの続きである。
OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。
ここでは OpenLayers 3.7.0 を使っている。
 前回(その2:地図の不透明度を変える)では, 地図の不透明度を変える方法について述べた。
今回は地図上に折線を引く方法を示そう。 地図上に折線を引ければ,任意の経路を地図上に表示できることになる。

 地図上に折線を引く方法はいくつか可能だと思われるが,ここでは vector layer を用いて表示している。 折線は連続した直線の集まりである。 OpenLayers 3 では,直線のデータとして始点の座標と終点の座標を要素に持つ配列を用いる。 座標(coordinate)自体が要素が2個の配列なので,一本の直線は2次元配列として表現される。 具体的には
直線を表す配列
[[A_longitude, A_latitude], [B_longitude, B_latitude]]
と表せる。
 これを,折線にするには,この直線用の配列が要素となる配列を用いる。 つまり3次元の配列をもちいて,折線を表現している。 具体的に示すと下記のような配列を用いて表現していることになる。
折線(直線の集合)を表す配列
[[[P1_lon, P1_lat], [P2_lon, P2_lat]],
 [[P2_lon, P2_lat], [P3_lon, P3_lat]], 
 [[P3_lon, P3_lat], [P4_lon, P4_lat]], 
 ...
]

ここから折線を表すタイプ ol.geom.MultiLineString クラスを構成するのだが,それには2通りのやり方がある。
 (1) 1つは,折線用の配列(3次元配列)から直接 ol.geom.MultiLineString クラスの変数(インスタンス)を作る方法である。
   その場合は,setCoordinates() 関数を用いて,3次元配列から MultiLineString 変数を得る。
 (2) 2つ目のやり方は,直線を表す2次元配列から setCoordinates() 関数で ol.geom.LineString クラスの変数(インスタンス)を作り,
   appendLineString() で ol.geom.MultiLineString クラスの変数を作る方法である。

 これら2つの方法の違いは,(1) だと自分で2次元配列から3次元配列を作らないといけないのと, 複数の折線を登録する方法がわからない(私にはわからない)のに対し, (2) だと,2次元配列から LineString 変数を作ってから,それを MultiLineString 変数に append(追加)している点である。 LineString を追加していくので,複数の線を追加可能である。
 今回は,3次元配列から直接 ol.geom.MultiLineString クラスの変数を作る方法((1) の方法)を使っている。 理由は,こちらの方が点の削除がスムースにできたから,である。 もしかしたら他に簡単にやる方法があるのかもしれないが…。

 MultiLineStrings 変数ができれば,それを要素とする ol.Feature クラスの変数を作る。 この Feature 変数が描画したい図形の「特徴」を示している。 さらにこの ol.Feature クラスの変数を要素に持つ ol.source.Vector クラスの変数を作り, この ol.source.Vector クラスの変数を source として持つ vector layer を作る。 そして,その vector layer を map.addLayer() 関数で地図に描画する,という手順を取る。
 少しややこしいが,こうすれば確かに線が引ける。

 以下に,具体的なコードを書こう。 まず,いつものように以下に 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 Line</title>
<script src="ol3ex3.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="addLine" onclick="addLine();">Draw a Line</button>
      <br>
      &nbsp;clicked coordinate:<span id="outStr"></span>
    </div>
  </div>

</body>
</html>
 web ページの変更点はごくわずかであり,「線の描画」のボタンを用意しただけである。

 次に JavaScript を載せよう。ここでも変更箇所の色を変え,説明は web ページのソースと同じくこの下に書いておく。
// ===================================================================
var map = null;      // 全体の地図用の変数
var cyberJ = 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() {
// 表示用の 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);
      var outstr = stringifyFunc(ol.proj.transform(coordinate, "EPSG:3857", "EPSG:4326"));
      document.getElementById('outStr').innerHTML = outstr;
    });

// 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 addLine() {
    var lineStrArray = new Array();
    var coordArray = new Array();

    coordArray.push([134.7061693665156,34.61224815577299]);
    coordArray.push([134.98357415167186,34.81544168178658]);
    coordArray.push([135.25304424804563,34.79564582556284]);
    coordArray.push([135.55242168945185,34.622921754425874]);
    coordArray.push([135.4288254980456,34.39459437355049]);
    coordArray.push([135.15004742187372,34.48180482108414]);
    coordArray.push([134.7366868261706,34.47954076626441]);

    document.getElementById("outStr").innerHTML = coordArray.length+" pts";

    for (i=0; i<(coordArray.length-1); i++) {
        lineStrArray.push([coordArray[i], coordArray[i+1]]);
    }

    var lineStrings = new ol.geom.MultiLineString([]);
    lineStrings.setCoordinates(lineStrArray);

    var vectorFeature = new ol.Feature(
        lineStrings.transform('EPSG:4326', 'EPSG:3857')
    );

    var vectorSource  = new ol.source.Vector({
        features: [vectorFeature]
    });

// 経路用の vector layer の作成
    var lineVector = new ol.layer.Vector({
        source: vectorSource,
        style: new ol.style.Style({
//            fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.2)' }),
            stroke: new ol.style.Stroke({ color: lineColor, width: 2 }),
//            image: new ol.style.Circle({ radius: 7, fill: new ol.style.Fill({ color: '#ffcc33' }) })
        })
    });

    // vector layer の追加
    map.addLayer(lineVector);
}
// *******************************************************************
 変更点について説明を書こう。

 (1) まず,「map」と「lineColor」という2つの変数をグローバルな変数として定義している。
   map 変数は,init_map() 関数の外で定義されている addLine() 関数で使いたいので,init_map() 関数の外で定義したものである。
   lineColor 変数は,描画したい線の色を設定している。ここで設定しているのは,後で線の色を変えたい時に変えやすいようにである。

 (2) (1) に伴って,init_map() 関数内の map の設定の文から「var」を取り除いている。

 (3) 「addLine() 関数」が,地図に線を描画するメインのパートである。
   まず coordArray() という配列を用意し,そこに折線を構成する「点」のデータを並べて入力する。
   次に緑色で示した部分で,折線用の配列 lineStrArray にデータを入力している。
   このように「点の配列」から「折線用の配列」を構成する方が,直接折線用の配列を用意するよりも,わかりやすい。

   次に紫色で示した部分で,折線用の配列から ol.geom.MultiLineString クラスの変数 lineStings を作っている。
   これは数値配列を「線の形式」で保持した変数と考えられる。
   さらに lineStings を要素に持つ vectorFeature を構成し,vectorFeature を要素に持つ vectorSource を構成している。
   vectorFeature は,線の座標情報など,描画対象図形の特徴(情報)を持つ変数である。
   vectorSource は,描画対象図形のソース(素)であり,描画する Layer 等に合わせた形式のソースを形成している。
   ここで,lineStings から vectorFeature を構成する際には,投影法をメルカトル図法から WGS84 に変換しておかないといけない。

   最後に vectorSource を使って lineVector という vector layer を作っている。
   そして map.addLayer(lineVector) とすると,地図上に折線が描画される。

 style についてコメントしておこう。ここでは vector layer の作成のところで, 描画図形の style を記述しているが,ここでは線しか引かないので,fill や image はコメントアウトしてある。 style は線などの図形の色や線幅などを指定するものであり,ol.style に記載がある。 そこにデフォルトの記載がある。
var fill = new ol.style.Fill({
    color: 'rgba(255,255,255,0.4)'
});
var stroke = new ol.style.Stroke({
    color: '#3399CC',
    width: 1.25
});
var styles = [
    new ol.style.Style({
        image: new ol.style.Circle({
            fill: fill,
            stroke: stroke,
            radius: 5
        }),
        fill: fill,
        stroke: stroke
    })
];
 詳しくは ol.style を見て欲しい。

 これで,「折線の描画」ボタンを押すと,折線が描画される。 ここで,もし,点を加えるなどして,折線に変更を加えたい時には,vectorFeature を作りなおせばよい。 つまり vectorFeature に変更が加えられれば,自動的に折線は再描画されるので, ここでの手順のすべてをもう一度実行する必要はない。

 「その3」のサンプルを具体的な web ページとして用意したので,具体的な表示を見てみて欲しい。 サンプルでは,右の白い領域の上の方にある「Draw a Line」というボタンを押すと線が描画され,地図上の点をクリックすると位置情報が表示される。 (ちなみにサンプルページはアクセスログを取るルーチンを組み込んでいます)

その4:マウスクリックで地図上に経路を描画するに続く

0 件のコメント: