Bingmapsでルートを記録→地図に描画 する


最近やたらBingMapsを多用したので折角なのでまとめてみたいと思います。
以下成果物を作成する過程を流れと共に共有したいと思います。

###成果物
Geolocation APIとBingmaps APIを組み合わせて、自分が走ったルートを記録し、地図に描画するプログラム

###実装の流れ
1.GeoLocation APIで現在地の座標を取得、現在地の地図を描画←今回はこちら
2.Bingmaps APIのDirection Moduleを使ってルート検索機能を実装
3.GeoLocation APIを使用し、移動ルートを記録
4.記録したルートをBingmapsAPIに渡し、地図上に描画
5.見やすく諸々微調整

##手順１：現在地座標の取得と地図を描画
第一段階としてGeolocation APIへリクエストし、自身の現在地座標を取得後、Bingmaps APIへ座標情報を元に現在地の地図を描画してもらいます。

```html:sample.html
<body>
    <h1>BingMaps TEST</h1>
    <div id="myMap"></div>
</body>
<!------bingmaps起動-------------->
<script src='https://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=[自身のKey#]' async defer></script>
<script src='sample.js'></script>
```
上記Key#についてはBingmapsアカウントを作成し取得してください。
こちらの記事で紹介されている手順で取得出来ます↓
https://qiita.com/ShingoOikawa/items/4ffb4bec13b352d0ff90

```javascript:sample.js
//---------現在位置が取得成功時に実行する関数--------
let map;
function mapsInit(position) {
  const lat = position.coords.latitude;
  const lon = position.coords.longitude;  
  map = new Microsoft.Maps.Map('#myMap', {
          center: new Microsoft.Maps.Location(lat, lon), 
          mapTypeId: Microsoft.Maps.MapTypeId.load, 
          zoom: 15 
      });
  pushpin(lat,lon,map);
}

//-----現在地にピンを描画--------------
function pushpin(la,lo,now){
  let location = new Microsoft.Maps.Location(la,lo)
  let pin = new Microsoft.Maps.Pushpin(location, {
  color: 'red',            
  draggable:false,          
  enableClickedStyle:true, 
  enableHoverStyle:true,   
  visible:true             
  });
  now.entities.push(pin);
};

//---------現在位置が取得失敗時に実行する関数--------
function mapsError(error) {
let e = "";
if (error.code == 1) { 
  e = "位置情報が許可されてません";
}
if (error.code == 2) { 
  e = "現在位置を特定できません";
}
if (error.code == 3) { 
  e = "位置情報を取得する前にタイムアウトになりました";
}
alert("エラー：" + e);
};

//-----------------現在地取得と地図描画-----------
function GetMap() {
navigator.geolocation.getCurrentPosition(mapsInit, mapsError);
}

```
geolocation APIの　getCurrentPositionメソッドの成功時に実行される関数mapsInitにてBingmaps APIへ描画する地図の中心座標（現在地）を渡し描画してもらいます。

その際にBingmapsのPushpinメソッドにて中心地(現在地)にピンを表示してもらっています。


##手順２：Bingmapsのルート検索機能追加
ルート検索機能はBingmaps側で既に用意されているDirection Moduleというモジュールがあるのでそちらを使用します。htmlと手順１で定義した関数mapsInitに下記「Direction Module入手」の部分を追記するだけです。

```html:sample.html
<body>
    <h1>BingMaps TEST</h1>
<!------Direction Module入手------------->
    <div class="directionsContainer">
        <div id="directionsPanel"></div>
        <div id="directionsItinerary"></div>
    </div>
    <div id="myMap"></div>
</body>
```

```javascript:sample.js
function mapsInit(position) {
  const lat = position.coords.latitude;
  const lon = position.coords.longitude;  
  map = new Microsoft.Maps.Map('#myMap', {
          center: new Microsoft.Maps.Location(lat, lon), 
          mapTypeId: Microsoft.Maps.MapTypeId.load, 
          zoom: 15 
      });
  pushpin(lat,lon,map);

//----------------Direction Moduleの入手-----------------
//------------------------------------------------------
  Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function () {
        directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
  directionsManager.setRenderOptions({itineraryContainer:'#directionsItinerary'});
        directionsManager.showInputPanel('directionsPanel');
    });
//------------------------------------------------------
}
```
ここまでだけでほぼほぼMapアプリの基本的な機能は実現出来ます。

##手順３：移動ルートの記録

次に自分の走ったルート情報を記録します。
スタートボタン、ストップボタンを実装し、スタートボタンを押してからストップボタンを押すまでの自身の座標データを記録します。今回は記録したデータをlocalStorageに保存します。

```html:sample.html
<body>
    <h1>BingMaps TEST</h1>
    <div class="directionsContainer">
        <div id="directionsPanel"></div>
        <div id="directionsItinerary"></div>
    </div>
    <div id="myMap"></div>
<!----------スタート、ストップボタン追加----------------->
         <button id="start_btn">スタート</button>
         <button id="stop_btn">ストップ</button>
</body>
```
```javascript:sample.js
//--------------------ルート記録スタート-------------------
let watchID;
let startWatch;
let key=0;
$("#start_btn").on('click',function(){
  localStorage.clear();
  let lat;
  let lon;
  watchID = navigator.geolocation.watchPosition(function(pos){
  lat = pos.coords.latitude;
  lon = pos.coords.longitude;
  },mapsError);
  startWatch=setInterval(function(){
    let value = lat+","+lon;
    localStorage.setItem(key,value);
    key++;
  },5000);
})

//--------------------ルート記録ストップ-------------------
$("#stop_btn").on('click',function(){
    clearInterval(startWatch);
    navigator.geolocation.clearWatch(watchID);
    let run_record="";
    for(i=0;i<(localStorage.length-1);i++){
        let data=localStorage.getItem(i);
        run_record += data+":"; 
    };
    run_record+=localStorage.getItem(key);
    key+=1;
    localStorage.setItem(key,run_record);
})
```
スタートボタンをクリックしgeolocation APIのwatchPositionを着火、自分のいる位置座標を常に監視します。その位置座標をsetIntervalで5秒おきに取得しlocalStorageに記録。

ストップボタンのクリックと共にwatchPositionとsetIntervalを終了させ、localStorageに記録されている座標データを：（コロン）区切りで繋ぎ合わせ、一つのデータとしてlocalStorageの末尾に追加。

watchPositionもsetIntervalも着火時に帰ってくる返り値を変数に代入しておき、clearInterval、clearWatchに渡す事で終了させます。


##手順４：記録したルートを地図上に描画

```html:sample.html
<body>
    <h1>BingMaps TEST</h1>
    <div class="directionsContainer">
        <div id="directionsPanel"></div>
        <div id="directionsItinerary"></div>
    </div>
    <div id="myMap"></div>
         <button id="start_btn">スタート</button>
         <button id="stop_btn">ストップ</button>
         <!-----00:ルート表示ボタンを追加------->
         <button id="write_btn">ルート表示</button>

</body>
```
```javascript:sample.js
$("#write_btn").on('click',function(){
    //-----01:localStorageより座標データ束の取得--------------      
    let data1 = (localStorage.getItem(key)).split(":"); 
    data1.pop();
    let data2=[];
    //---------------02:座標データを25個に調整-------------
    if(data1.length>25){
      data2.push(data1[0]);
      if(data1.length%25 != 0){
        let num1=data1.length/25;
        let count=1;
        while(count<24){
          data2.push(data1[Math.round(count*num1)]);
          count++;
        }
        data2.push(data1.slice(-1)[0]);
      }else{
        let num2=data1.length/25;
        for(i=num2;i<25;i+num2){
          data2.push(data1[i]);
        }
      }        
    }else{
      data2=data1;
    }
    //--------------03:中継点の座標データを追加----------------
    for(i=0;i<data2.length;i++){
      let array = (data2[i]).split(",");
      let lati=array[0];
      let long=array[1];
      Waypoint = new Microsoft.Maps.Directions.Waypoint({ location:new Microsoft.Maps.Location(lati,long)});
      directionsManager.addWaypoint(Waypoint);
    } 

    //------------04:描画ルートを歩行ルートに指定--------------
    let mode= Microsoft.Maps.Directions.RouteMode.walking;
        directionsManager.setRequestOptions({
        routeMode: mode
    });
    
    //------------05:地図へルートを描画-----------------------
    directionsManager.calculateDirections();    
})

```
ルートを地図に描画するに際して、BingmapsのDirection moduleに中継点として通ったルートの座標を渡し描いてもらいます。

Direction moduleでは中継点が25点までしか追加する事ができない為、取得した座標の数が25を超える場合、調整が必要となります。

00:ルート表示ボタンを追加。
01:前手順でlocalStorageの末尾に追加した座標データ束を取得し、配列化。
02:中継点の数が25点になる様に調整。
03:25点に絞った中継点(Waypoint)をBingmaps APIに登録。
04：今回は足で走ったルートを想定しているので、歩行時のルートを描画する設定。walkingの代わりにdrivingにすれば車のルートに変更可能です。
05:上記までの登録内容に従って地図上にルートを描画。

これでかなり雑ではあるけれど記録した自分の歩行ルートを地図上に描画する事が出来た。

##手順５：見やすく微調整

地図上にルートを描画出来ましたがこの状態だと色々と見にくいので無駄なものを非表示にし、もう少し見やすくしたいと思います。

消すのは主に、①地図上の機能ボタン、②表示されているDirection Module入力欄、③中継点の座標ピン、です。

①地図上の機能ボタンの非表示
個人的に必要ないと思ったズームボタン、MapTypeSelector、スケールバーなどを非表示にしました。
②表示されているDirection Module入力欄の削除
単純に前段階で記入していたdirectionsManager.showInputPanel('directionsPanel');の記述を削除。
③中継点の座標ピンの非表示
RenderOptionメソッドにて最初と最後の座標ピンだけを表示し、その間の中継点を非表示に設定。

```javascript:sample.js
function mapsInit(position) {
  const lat = position.coords.latitude;
  const lon = position.coords.longitude;  
  map = new Microsoft.Maps.Map('#myMap', {
          center: new Microsoft.Maps.Location(lat, lon), 
          mapTypeId: Microsoft.Maps.MapTypeId.load, 
          zoom: 15,
 //----------①地図上の機能ボタンを非表示-------------//
          showZoomButtons:false,    
          showMapTypeSelector:false,
          showScalebar:false,
          showTermsLink:false
      });
  pushpin(lat,lon,map);

  Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function () {
        directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
  directionsManager.setRenderOptions({itineraryContainer:'#directionsItinerary'});
//----------②input Panelの消去---------------//
//----------③中継点の座標ピンの非表示-------------//
  directionsManager.setRenderOptions({
            firstWaypointPushpinOptions:{visible:true},
            lastWaypointPushpinOptions:{visible:true},
            waypointPushpinOptions:{visible:false}
        });  
    });
}
```
これで見た目もスッキリしたのではないでしょうか。今回非表示に変更したのはあくまでも個人的な理由なので、その他のオプションも含め下記公式ドキュメントを参考にご自身でカスタマイズしてみてください。

MapOptionsオブジェクト
https://docs.microsoft.com/en-us/bingmaps/v8-web-control/map-control-api/mapoptions-object

Directions RenderOptionsオブジェクト
https://docs.microsoft.com/en-us/bingmaps/v8-web-control/modules/directions-module/directionsrenderoptions-object

##最後に
以上で雑ですが、簡単なルート走行を記録し、地図上に描画するプログラムが作成できます。

Bingmapsはあまり活用している人が少ないのか英語の文献が中心で調べるのに少々苦労しました。GoogleMapの方が参考文献は多いですが、今後有料化の流れの中で簡単なアプリケーションを作るだけならBingmapsを活用する事も多くなるのではないかと思います。

Bingmaps公式ドキュメント：
https://docs.microsoft.com/en-us/bingmaps/rest-services/