Cordovaによるハイブリッドアプリの開発(2)

この記事は1年以上前に書かれました。
内容が古くなっている可能性がありますのでご注意下さい。


前回Apache Cordovaを使用して作成した地図アプリでは、常に北が上になるように地図を表示しました。今回は、カーナビのように進行方向が上になるよう、デバイスで取得した方位を用いて地図を回転させることにします。

※OpenLayers 3については、現在公開されている正式版とは仕様が異なる点がありますのでご注意下さい。

前回はOpenLayers 2を使用しましたが、今回はOpenLayers 3のgamma版を使用します。
完成したアプリの画面は以下の通りです。

地図タイルの画像を回転させているため、文字も一緒に回転してしまいます。そこで、コンパスをタップして地図の回転有無(北が上か、進行方向が上か)を切り替えられるようにします。

index.htmlは以下のようになります。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Simple Navi</title>
  <link rel="stylesheet" type="text/css" href="navi.css">
  <link rel="stylesheet" type="text/css" href="ol.css">
  <script type="text/javascript" src="cordova.js"></script>
  <script type="text/javascript" src="jquery-2.1.1.min.js"></script>
  <script type="text/javascript" src="ol.js"></script>
  <script type="text/javascript">

    var ready = false;
    var timer = 0;
    var watch;
    var map;
    var view;
    var zoom = 17;
    var maprotate = true;

    $(function() {
      view = new ol.View();
      var map = new ol.Map({
        target: 'mapdiv',
        view: view,
        controls: new ol.control.defaults({zoom: false, rotate:false})
      });
      var osmLayer = new ol.layer.Tile({
        source: new ol.source.OSM()
      });
      map.addLayer(osmLayer);
      map.on('moveend', moveend);

      document.addEventListener('deviceready', function() {
        ready = true;
        $("#btndiv").css("color", "black");
      } );
    } );

    function btnClick(btn) {
      if(ready) {
        switch (btn) {
          case 'toggle':
            toggle();
            break;
          case 'refresh':
            refresh();
            break;
          case 'plus':
            if(zoom < 19) {
              zoom++;
              view.setZoom(zoom);
            }
            break;
          case 'minus':
            if(zoom > 1) {
              zoom--;
              view.setZoom(zoom);
            }
            break;
        }
      }
    }

    function toggle() {
      if(!timer) {
        watch = navigator.compass.watchHeading(
          function (heading) {
            if(maprotate) {
              view.setRotation( -(heading.magneticHeading * Math.PI / 180));
            }
            $("#compass")
              .css("transform", "rotate(-" + heading.magneticHeading + "deg)");
//          showMsg('watchHeading', heading.magneticHeading);
          },
          function (err) {
            showMsg('watchHeading', err.message);
          },
          {frequency: 500}
        );
        refresh();
        timer = setInterval('refresh()', 10000);
        toggleButton.innerHTML = 'Stop';
      } else {
        clearInterval(timer);
        navigator.compass.clearWatch(watch);
        timer = 0;
        toggleButton.innerHTML = 'Start';
      }
    }

    function refresh() {
      navigator.geolocation.getCurrentPosition(
        function(pos) {
          view.setCenter(
            ol.proj.transform([pos.coords.longitude, pos.coords.latitude],
              'EPSG:4326', 'EPSG:3857'
            )
          );
          view.setZoom(zoom);
//          showMsg('getCurrentPosition',
//            pos.coords.longitude + ', ' +  pos.coords.latitude);
        },
        function(err) {
          showMsg('getCurrentPosition', err.message);
        },
        {maximumAge: 10000, timeout: 5000, enableHighAccuracy: true}
      );
    }

    function moveend() {
      zoom = view.getZoom();
    }

    function toggleRotation() {
      if(maprotate) {
        view.setRotation(0);
        maprotate = false;
      } else {
        maprotate = true;
      }
    }

    function showMsg(func, msg) {
      var d = new Date();
      statusdiv.innerHTML = 
        ('0'+d.getHours()).slice(-2) + ':' +
        ('0'+d.getMinutes()).slice(-2) + ':' +
        ('0'+d.getSeconds()).slice(-2) + ' ' +
        func + ' : ' + msg + '<br />' + statusdiv.innerHTML;
    }

  </script>
</head>
<body>
  <div id="btndiv">
    <a onClick="btnClick('toggle');" id="toggleButton">Start</a>
    <a onClick="btnClick('refresh');" id="refreshButton">Refresh</a>
    <a onClick="btnClick('minus');" id="zoomButton">-</a>
    <a onClick="btnClick('plus');" id="zoomButton">+</a>
  </div>
  <div id="mapdiv">
    <a onClick="toggleRotation();">
      <img id="compass" src="compass.png"></img>
    </a>
    <img id="crosshairs" src="crosshairs.png"></img>
  </div>
  <div id="statusdiv"></div>
</body>
</html>

body部の変更は、地図の回転有無を変更するtoggleRotation()関数を呼び出すためのaタグでコンパスのimgを括ったことと、十字(crosshairs)カーソルのpng画像をimgタグで読み込んでいることです。
十字カーソルを地図の中央に表示するため、CSSファイルに以下の記述を追加します。

#crosshairs {
  position: absolute;
  left: 50%;
  top: 50%;
  margin-left: -16px;
  margin-top: 0;
  z-index: 10000;
}

スクリプトの変更は、ほとんどはOpenlayers 2からOpenlayers 3への変更によるものです。
関数の追加は、地図の回転有無を変更するtoggleRotation()関数のみです。
toggleRotation()関数で回転なしにする場合は、

view.setRotation(0);

で即座に北を上にします。回転ありにする場合はmaprotateフラグを書き換えておき、次に方位取得したタイミングで回転させます。

地図の回転は、navigator.compass.watchHeading()による方位取得が成功したときのコールバック処理の中で

view.setRotation( -(heading.magneticHeading * Math.PI / 180));

を呼び出すだけで実現できます。

ハイブリッドアプリの開発 123