Leafletのマーカーを変更する

#はじめに
このページは，

[ドローン操作システムを作ろう](https://qiita.com/hsgucci/items/86eedb5555b4234ee0e7)

の１ページです．
全体を見たい場合は上記ページへお戻りください．

#概要

前回は，Leafletを利用した地図を表示させ，
MQTTで受信したドローンの位置情報を元にマーカーを打ってみました．

しかし，**<font color="red">受信するたびに新規マーカーを作ってリソースを食っていくポンコツプログラム</font>**でした．

今回はこのマーカー部分に関して改善していきます．

#マーカーに画像ファイルを使う

ドローンを操作するのですから，地図上に表示されるマーカーはやはりドローンらしい物が望ましいですね．
Leafletのマーカーは，画像ファイルに簡単に替えることができます．

##画像ファイルの準備
今回はこの画像を使いましょう．
え？「[どこか](https://docs.px4.io/en/config/airframe.html)で見たことがある」って？気のせいですよ(笑
![quad_x-90.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/8cba3cd4-6f7a-6fb3-e0b4-b488710b5af7.png)
まずは画像ファイルをダウンロードしておきましょう．
画像を 右クリック-[名前を付けて画像を保存] するか，
[このリンク](https://raw.githubusercontent.com/DCoJA/OpenUVTM/master/chapter12/quad_x-90.png) を 右クリック-[名前を付けて保存] してください．

保存した`quad_x-90.png`を，Webサーバーが利用するフォルダへコピーします．
管理者権限が必要なので`sudo`を付けます．

```bash
$sudo cp quad_x-90.png /var/www/html/
```

##HTMLファイルの準備
次はHTMLファイル本体です．

[このリンク](https://raw.githubusercontent.com/DCoJA/OpenUVTM/master/chapter12/maker_icon.html) を右クリック-[名前を付けて保存] するか，
以下のソースコードをコピーペーストします．

```html:maker_icon.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" /><!-- 文字コードはutf-8を使用する -->
	<title>Change Marker Icon</title><!-- タイトルバーに表示されるメッセージ -->

    <!-- 以下の2行でLeafletで使用するスタイルシート(.css)とライブラリ(.js)を読み込む -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js" integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og==" crossorigin=""></script>

    <script src="MovingMarker.js" type="text/javascript"></script>
    <script src="leaflet.rotatedMarker.js" type="text/javascript"></script>


    <!-- 以下の2行で，MQTT over Websocketを使うライブラリ(.js)を読み込む -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
    <script src="mqttws31.js" type="text/javascript"></script>

    <!-- echo.htmlにあったスタイルシートをコピー -->
    <style>
      .box {
          width: 440px;
          float: left;
          margin: 0 20px 0 20px;
      }

      .box div, .box input {
          border: 1px solid;
          -moz-border-radius: 4px;
          border-radius: 4px;
          width: 100%;
          padding: 5px;
          margin: 3px 0 10px 0;
      }

      .box div {
          border-color: grey;
          height: 300px;
          overflow: auto;
      }

      div code {
          display: block;
      }

      #second div {
          font-size: 0.8em;
      }
    </style>
</head>

<body>
<!-- このmapidと名付けられたdiv要素の中に地図を表示する -->
<div id="mapid" style="width: 950px; height: 400px;"></div>

<!-- エディットボックスを作る部分．#firstの方は削除した -->
<div id="second" class="box">
  <h2>Logs</h2>
  <div></div>
</div>

<!-- <script></script>で囲まれた部分がJavaScript -->
<script>
    // leafletのスクリプト部分

    // mapidと名の付いたdiv要素に地図を作成し，視点は柏の葉キャンパス駅前付近，ズームレベルは16に設定
	var mymap = L.map('mapid').setView([35.894087,139.952447], 17);

    // OpenStreetMapのタイルレイヤーを作る
    var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
        attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
        maxZoom: 19
    });
    tileLayer.addTo(mymap); // 作成したtileLayerをmymapに追加する

    // マーカーにする画像を読み込む
    var quad_x_Icon		= L.icon({ iconUrl: 'quad_x-90.png', iconRetinaUrl: 'quad_x-90.png', iconSize: [50, 50], iconAnchor: [25, 25], popupAnchor: [0, -50] });

    // MQTT over WebSocketのスクリプト部分
    var has_had_focus = false;
    var pipe = function(el_name, send) {
        var div  = $(el_name + ' div');
        var inp  = $(el_name + ' input');
        var form = $(el_name + ' form');

        var print = function(m, p) {
            p = (p === undefined) ? '' : JSON.stringify(p);
            div.append($("<code>").text(m + ' ' + p));
            div.scrollTop(div.scrollTop() + 10000);
        };
        return print;
    };

    var debug = pipe('#second');

    var wsbroker = location.hostname;  //mqtt websocket enabled broker
    var wsport = 15675; // port for above

    var client = new Paho.MQTT.Client(wsbroker, wsport, "/ws", "myclientid_" + parseInt(Math.random() * 100, 10));

    client.onConnectionLost = function (responseObject) {
        debug("CONNECTION LOST - " + responseObject.errorMessage);
    };

    // MQTTメッセージSubscribe時の処理
    client.onMessageArrived = function (message) {
        debug("RECEIVE ON " + message.destinationName + " PAYLOAD " + message.payloadString);

        var drone_name = message.destinationName;   // ドローン名はトピック名とする
        var drone_data = JSON.parse( message.payloadString );   // ドローンのデータを連想配列にして格納

        var mode = drone_data.status.Arm;           // ARM/DISARM
        var arm  = drone_data.status.FlightMode;    // フライトモード
        var lat  = parseFloat( drone_data.position.latitude );  // 緯度
        var lon  = parseFloat( drone_data.position.longitude ); // 経度
        var alt  = parseFloat( drone_data.position.altitude );  // 高度
        var ang  = parseFloat( drone_data.position.heading );   // 方位

        // ポップアップ用に文字列を作る +=で追加していく
        var drone_popmessage = drone_name + '<br>';
        drone_popmessage += mode + ',' + arm + '<br>';
        drone_popmessage += lon + ',' + lat + '<br>';
        drone_popmessage += alt + '[m], ' + ang + '[deg]<br>';

        // (lat, lon)の座標にマーカーを作り，icon情報を与え，ポップアップメッセージを追加する
        L.marker( [ lat, lon],{ icon:quad_x_Icon } ).addTo(　mymap　).bindPopup( drone_popmessage );

    };

    var options = {
        timeout: 3,
        onSuccess: function () {
            debug("CONNECTION SUCCESS");
            client.subscribe('drone/#', {qos: 1});
        },
        onFailure: function (message) {
            debug("CONNECTION FAILURE - " + message.errorMessage);
        }
    };

    if (location.protocol == "https:") {
        options.useSSL = true;
    }

    debug("CONNECT TO " + wsbroker + ":" + wsport);
    client.connect(options);
</script>

</body>
</html>
```
保存したmarker_icon.htmlを，Webサーバーが利用するフォルダへコピーします．
管理者権限が必要なのでsudoを付けます．

```bash
$sudo cp marker_icon.html /var/www/html/
```

###解説
前回の記事で最後に作った`hello_drone.html`からの変更点は，
**<font color="blue">２箇所だけです</font>**．

1つ目は，**77行目**です．

```javascript:マーカーに使う画像ファイルの読み込み
var quad_x_Icon	= L.icon({ iconUrl: 'quad_x-90.png', iconRetinaUrl: 'quad_x-90.png', iconSize: [50, 50], iconAnchor: [25, 25], popupAnchor: [0, -50] });
```

`L.icon`関数で，マーカー(アイコン)に使う画像ファイルを読み込みます．
それぞれの要素は，
<font color="darkGreen">iconUrl</font>：一般的なPCのWebブラウザで使用する画像
<font color="darkGreen">iconRetinaUrl</font>：Retinaディスプレイやそれに類する高精細ディスプレイの時に使用する画像
<font color="darkGreen">iconSize</font>：表示する際のアイコンの大きさを[幅：高さ]で指定する．
　　　　　画像ファイルのサイズが違っても，この大きさに自動的に拡大縮小される．
<font color="darkGreen">iconAnchor</font>：画像中のどの画素が実際にその緯度経度の真上に来るかを決める項目．[x,y]で与える．
　　　　　　　例えば，画ビョウの画像ではピンの先の位置が与えた緯度経度になって欲しい．
　　　　　　　しかし，ドローン画像では，機体の中心が与えた緯度経度になって欲しい．
<font color="darkGreen">popupAnchor</font>：ポップアップ(吹き出し)の表示される位置を指定する．
　　　　　　　マーカーの形状によって表示したい場所が違うので．

２つ目は**126行目**です．

```javascript:マーカーの作成時のオプションを追加
L.marker( [lat, lon],{ icon:quad_x_Icon } ).addTo( mymap ).bindPopup( drone_popmessage );
```
`hello_drone.html`の時は
`L.marker([lat,lon]).addTo(mymap).bindPopup(drone_popmessage);`
でしたが，`L.marker`関数の引数が追加されて`,{ icon:quad_x_Icon }`が増えています．
これで，77行目で作ったアイコンを使う様に命令しています．

##Pubプログラムの実行
[dronekitの情報をMQTTで送信してみる](https://qiita.com/hsgucci/items/ec3df139cfa23da4ea0a)
で作った`sitl_mqtt_pub_json.py`を起動して，
ブローカーにメッセージを投げて(Pubして)おきます．

```bash:sitlドローンの位置情報をPubするプログラム
$python sitl_mqtt_pub_json.py
```

##実行結果

Webブラウザのアドレスバーに
`http://localhost/marker_icon.html`
と入力し，Enterしてページを表示させます．

こんな画面が表示されれば成功です．
見事にドローンのマーカーに替わっています．
![Screenshot at 2019-05-22 18-32-26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/e555e8d1-ca69-71f8-f4c1-adfffdf8d861.png)

しかし，Pub側プログラムを操作して，
GUIDED->ARM->TakeOff->simple_goto
してみると，こんな風になってしまいます．
![Screenshot at 2019-05-22 18-42-47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/ad92e615-ca83-fbb8-3bcb-26437348e118.png)

マーカーを変更しただけで，
**<font color="red">受信するたびに新規マーカーを作ってリソースを食っていくポンコツ</font>**
な部分はまだ改善していませんので，
**以前の標準マーカーの時よりも残念な画面**になっています．．．

もっと格好良く表示したいものです．

#動くマーカーを使う

マーカー画像をオリジナルの物に変更することはできました．
次は，**<font color="red">受信するたびに新規マーカーを打つ</font>**というポンコツ部分を，
**<font color="blue">1つのマーカーがリアルタイムに移動する</font>**様に変えてみたいと思います．
ついでに**<font color="green">ドローンの方位と同様にマーカーの方位も変化する</font>**様にもしてしまいます．

##Leafletプラグインの追加
マーカーを動く様にできるLeaflet用のプラグインがあります．

・マーカーの移動：[MovingMarkerプラグイン](https://github.com/ewoken/Leaflet.MovingMarker)
　　　　　　　　　　[ここ](https://github.com/ewoken/Leaflet.MovingMarker/blob/master/MovingMarker.js)　を右クリック-[名前を付けて保存]
・マーカーの回転：[RotatedMarkerプラグイン](https://github.com/bbecquet/Leaflet.RotatedMarker)
　　　　　　　　　　[ここ](https://raw.githubusercontent.com/bbecquet/Leaflet.RotatedMarker/master/leaflet.rotatedMarker.js)　を右クリック-[名前を付けて保存]

保存した`MovingMarker.js`と`leaflet.rotatedMarker.js`を，
Webサーバーが利用するフォルダへコピーします．
管理者権限が必要なので`sudo`を付けます．

```bash
$sudo cp MovingMarker.js /var/www/html/
$sudo cp leaflet.rotatedMarker.js /var/www/html/
```


##HTMLファイルの準備
次はHTMLファイル本体です．

[このリンク](https://raw.githubusercontent.com/DCoJA/OpenUVTM/master/chapter12/marker_moving_rotated.html) を右クリック-[名前を付けて保存] するか，
以下のソースコードをコピーペーストします．

```html:marker_moving_rotated.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8" /><!-- 文字コードはutf-8を使用する -->
	<title>Moving Marker</title><!-- タイトルバーに表示されるメッセージ -->

    <!-- 以下の2行でLeafletで使用するスタイルシート(.css)とライブラリ(.js)を読み込む -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin=""/>
    <script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js" integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og==" crossorigin=""></script>

    <script src="MovingMarker.js" type="text/javascript"></script>
    <script src="leaflet.rotatedMarker.js" type="text/javascript"></script>

    <!-- 以下の2行で，MQTT over Websocketを使うライブラリ(.js)を読み込む -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
    <script src="mqttws31.js" type="text/javascript"></script>

    <!-- echo.htmlにあったスタイルシートをコピー -->
    <style>
      .box {
          width: 440px;
          float: left;
          margin: 0 20px 0 20px;
      }

      .box div, .box input {
          border: 1px solid;
          -moz-border-radius: 4px;
          border-radius: 4px;
          width: 100%;
          padding: 5px;
          margin: 3px 0 10px 0;
      }

      .box div {
          border-color: grey;
          height: 300px;
          overflow: auto;
      }

      div code {
          display: block;
      }

      #second div {
          font-size: 0.8em;
      }
    </style>
</head>

<body>
<!-- このmapidと名付けられたdiv要素の中に地図を表示する -->
<div id="mapid" style="width: 950px; height: 400px;"></div>

<!-- エディットボックスを作る部分．#firstの方は削除した -->
<div id="second" class="box">
  <h2>Logs</h2>
  <div></div>
</div>

<!-- <script></script>で囲まれた部分がJavaScript -->
<script>
    // leafletのスクリプト部分

    // mapidと名の付いたdiv要素に地図を作成し，視点は柏の葉キャンパス駅前付近，ズームレベルは16に設定
	var mymap = L.map('mapid').setView([35.894087,139.952447], 17);

    // OpenStreetMapのタイルレイヤーを作る
    var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
        attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
        maxZoom: 19
    });
    tileLayer.addTo(mymap); // 作成したtileLayerをmymapに追加する

    // マーカーにする画像を読み込む
    var quad_x_Icon		= L.icon({ iconUrl: 'quad_x-90.png', iconRetinaUrl: 'quad_x-90.png', iconSize: [50, 50], iconAnchor: [25, 25], popupAnchor: [0, -50] });

    // MQTT over WebSocketのスクリプト部分
    var has_had_focus = false;
    var pipe = function(el_name, send) {
        var div  = $(el_name + ' div');
        var inp  = $(el_name + ' input');
        var form = $(el_name + ' form');

        var print = function(m, p) {
            p = (p === undefined) ? '' : JSON.stringify(p);
            div.append($("<code>").text(m + ' ' + p));
            div.scrollTop(div.scrollTop() + 10000);
        };
        return print;
    };

    var debug = pipe('#second');

    var wsbroker = location.hostname;  //mqtt websocket enabled broker
    var wsport = 15675; // port for above

    var client = new Paho.MQTT.Client(wsbroker, wsport, "/ws", "myclientid_" + parseInt(Math.random() * 100, 10));

    client.onConnectionLost = function (responseObject) {
        debug("CONNECTION LOST - " + responseObject.errorMessage);
    };


    // ドローン情報保存用の配列
    var drones = new Array();   // 緯度経度などのデータを保存する連想配列
    var markers = new Array();  // マーカーハンドラを保存する連想配列

    // MQTTメッセージSubscribe時の処理
    client.onMessageArrived = function (message) {
        debug("RECEIVE ON " + message.destinationName + " PAYLOAD " + message.payloadString);   //debugボックスに表示

        var drone_name = message.destinationName;   // ドローン名はトピック名とする

        var drone_data = JSON.parse( message.payloadString );   // ドローンのデータを連想配列にして格納

        var mode = drone_data.status.Arm;           // ARM/DISARM
        var arm  = drone_data.status.FlightMode;    // フライトモード
        var lat  = parseFloat( drone_data.position.latitude );  // 緯度
        var lon  = parseFloat( drone_data.position.longitude ); // 経度
        var alt  = parseFloat( drone_data.position.altitude );  // 高度
        var ang  = parseFloat( drone_data.position.heading );   // 方位

        // ポップアップ用に文字列を作る +=で追加していく
        var drone_popmessage = drone_name + '<br>';
        drone_popmessage += mode + ',' + arm + '<br>';
        drone_popmessage += lon + ',' + lat + '<br>';
        drone_popmessage += alt + '[m], ' + ang + '[deg]<br>';

       	// drone_nameで登録されている連想配列があるか？ ない->新規にマーカー作成 ある->なにもしない
	    if( !drones[drone_name] ) {

    		// movingMarkerでマーカーを作成し，rotatedMarkerのrotationオプションをつける
    		markerHandle = L.Marker.movingMarker([[lat, lon]], [], 
							{	rotationAngle: 0,   // 回転角度
								rotationOrigin: 'center center',    // 回転中心
								title:drone_name,
								contextmenu: true,
								contextmenuItems: [{
									text: drone_name,
									index: 0
								}, {
									separator: true,
									index: 1
								}]
							});
            markerHandle.bindPopup( drone_popmessage ); // ポップアップメッセージの設定
            markerHandle.options.icon = quad_x_Icon;    // マーカーアイコンをオリジナル画像に
            markerHandle.options.autostart = true;	    // MovingMarker機能を自動スタート
            markerHandle.addTo( mymap );                // 地図へ追加

        	markers[drone_name] = markerHandle;	// マーカー用連想配列に今回作ったマーカー情報を保存
        } else {
            markerHandle = markers[drone_name]  // 保存されているマーカー情報をdrone_name(トピック名)をキーにして呼び出す
		    markerHandle.moveTo([lat,lon], 1000);  // MovingMarkerの移動機能
		    markerHandle.setRotationAngle(ang);	   // ratatedMarkerの回転機能
            markerHandle.setPopupContent( drone_popmessage );	// メッセージ更新
        }
        drones[drone_name] = drone_data; // ドローンデータ用連想配列の情報を最新のメッセージに更新

    };

    var options = {
        timeout: 3,
        onSuccess: function () {
            debug("CONNECTION SUCCESS");
            client.subscribe('drone/#', {qos: 1});
        },
        onFailure: function (message) {
            debug("CONNECTION FAILURE - " + message.errorMessage);
        }
    };

    if (location.protocol == "https:") {
        options.useSSL = true;
    }

    debug("CONNECT TO " + wsbroker + ":" + wsport);
    client.connect(options);
</script>

</body>
</html>
```
保存したmarker_moving_rotated.htmlを，Webサーバーが利用するフォルダへコピーします．
管理者権限が必要なのでsudoを付けます．

```bash
$sudo cp marker_moving_rotated.html /var/www/html/
```

##プログラム解説

###head部
ヘッダー部には，新しく使う`MovingMarker`と`rotatedMarker`のプラグインの読み込みが追加されています．

```html:Leafletプラグインの追加
<script src="MovingMarker.js" type="text/javascript"></script>
<script src="leaflet.rotatedMarker.js" type="text/javascript"></script>
```
ローカルにある.jsファイルを使うので，srcに書くURLが短いですね．

###body部
ボディ部に変更はありません．

###script部
MQTTのメッセージ受信(Subscribe)時に毎回マーカーを作るのではなく，
1個のマーカーを動かす様に書き換えます．

まず，`client.onMessageArrived`関数の上に，グローバル変数の作成をします．

```javascript:グローバル変数
// ドローン情報保存用の配列
var drones = new Array();   // 緯度経度などのデータを保存する連想配列
var markers = new Array();  // マーカーハンドラを保存する連想配列
```
今回はドローン１台しか表示しないので，グローバル変数をArray(配列)にする必要はなかったのですが，
今後は**<font color="blue">複数ドローンの表示</font>**に対応したいので，Arrayで宣言しておきます．


`client.onMessageArrived`関数では，メッセージからトピック名とデータを取り出し，
ドローンの各種データを取り出し，ポップアップメッセージを作成するところ，
までは上述したプログラムと同じです．

```javascript:MQTTメッセージ受信時の処理は途中までは同じ
// MQTTメッセージSubscribe時の処理
client.onMessageArrived = function (message) {
    debug("RECEIVE ON " + message.destinationName + " PAYLOAD " + message.payloadString);   //debugボックスに表示

    var drone_name = message.destinationName;   // ドローン名はトピック名とする

    var drone_data = JSON.parse( message.payloadString );   // ドローンのデータを連想配列にして格納

    var mode = drone_data.status.Arm;           // ARM/DISARM
    var arm  = drone_data.status.FlightMode;    // フライトモード
    var lat  = parseFloat( drone_data.position.latitude );  // 緯度
    var lon  = parseFloat( drone_data.position.longitude ); // 経度
    var alt  = parseFloat( drone_data.position.altitude );  // 高度
    var ang  = parseFloat( drone_data.position.heading );   // 方位

    // ポップアップ用に文字列を作る +=で追加していく
    var drone_popmessage = drone_name + '<br>';
    drone_popmessage += mode + ',' + arm + '<br>';
    drone_popmessage += lon + ',' + lat + '<br>';
    drone_popmessage += alt + '[m], ' + ang + '[deg]<br>';
```

このif文からがドローンを1台だけ表示するためのポイントです．

```javascript:ドローンデータがまだ無いか/既にあるかで，新規マーカー作成/マーカー情報更新を分岐する
   	// drone_nameで登録されている連想配列があるか？ ない->新規にマーカー作成 ある->なにもしない
    if( !drones[drone_name] ) {

		// movingMarkerでマーカーを作成し，rotatedMarkerのrotationオプションをつける
		markerHandle = L.Marker.movingMarker([[lat, lon]], [], 
						{	rotationAngle: 0,   // 回転角度
							rotationOrigin: 'center center',    // 回転中心
							title:drone_name,
							contextmenu: true,
							contextmenuItems: [{
								text: drone_name,
								index: 0
							}, {
								separator: true,
								index: 1
							}]
						});
        markerHandle.bindPopup( drone_popmessage ); // ポップアップメッセージの設定
        markerHandle.options.icon = quad_x_Icon;    // マーカーアイコンをオリジナル画像に
        markerHandle.options.autostart = true;	    // MovingMarker機能を自動スタート
        markerHandle.addTo( mymap );                // 地図へ追加

    	markers[drone_name] = markerHandle;	// マーカー用連想配列に今回作ったマーカー情報を保存
    } else {
        markerHandle = markers[drone_name]  // 保存されているマーカー情報をdrone_name(トピック名)をキーにして呼び出す
	    markerHandle.moveTo([lat,lon], 1000);  // MovingMarkerの移動機能
	    markerHandle.setRotationAngle(ang);	   // ratatedMarkerの回転機能
        markerHandle.setPopupContent( drone_popmessage );	// メッセージ更新
    }
    drones[drone_name] = drone_data; // ドローンデータ用連想配列の情報を最新のメッセージに更新

};

```
グローバルdrones配列は`var drones = new Array();`で定義されているので，初期状態では中身はありません．
**drone_nameにはトピック名が入っています**から，このif文

```javascript:drones配列の中に，キーがdrone_nameのデータがあるか？ないか？
    if( !drones[drone_name] ) {
```
の中の`drones[drone_name]`は，
具体的には**drones["drone/001"]というデータがあるか？**という問い合わせになります．
初期状態では当然drones配列の中身はないので，結果は**false**になります．
しかし，**!**マークが付けてあるので，論理が反転します．
というわけで**drones["drone/001"]というデータは無いよね？**という問い合わせになり，答えは**true**．
概念的に書くと以下の様になります．

```c
if(drones["drone/001"]が「ない」) {  // 真のとき
   はじめて到着したデータなので，マーカーを新しく作る
} else {  // 偽のとき
   すでにデータが有る＝マーカーも有る，
   なので,マーカーは新しく作らず，既存のマーカーの情報のアップデートだけする
}

drones["drone/001"]データを作る(なので２回目の受信時は絶対にfalseになる)
```
---
マーカーの新規作成時の処理は，以下の様になります．

```javascript:マーカーの新規作成とグローバル変数への登録
// movingMarkerでマーカーを作成し，rotatedMarkerのrotationオプションをつける
markerHandle = L.Marker.movingMarker([[lat, lon]], [], 
				{	rotationAngle: 0,   // 回転角度
					rotationOrigin: 'center center',    // 回転中心
					title:drone_name,
					contextmenu: true,
					contextmenuItems: [{
						text: drone_name,
						index: 0
					}, {
						separator: true,
						index: 1
					}]
				});
markerHandle.bindPopup( drone_popmessage ); // ポップアップメッセージの設定
markerHandle.options.icon = quad_x_Icon;    // マーカーアイコンをオリジナル画像に
markerHandle.options.autostart = true;	    // MovingMarker機能を自動スタート
markerHandle.addTo( mymap );                // 地図へ追加

markers[drone_name] = markerHandle;	// マーカー用連想配列に今回```
```

1. `L.Marker.movingMarker`新規マーカーをmovingMakerで作成し，オプション設定にrotatedMarkerの条件を追加する
2. `bindPopup`：マーカーにポップアップメッセージを付加
3. `options.icon`：マーカーアイコンはドローンのアイコンに設定
4. `options.autostart`：MovingMarkerの移動は自動で始まる
5. `addTo`：地図へ追加

というマーカー作成処理を行った後で，

グローバル配列`markers`に，{トピック名`drone_name`:マーカーの管理ハンドル`markerHandle`}を追加しています．
これでメッセージの到着２回目以降は，このmarkerHandleを取り出して使用すれば，
既に存在しているマーカーを操作することが可能になります．

---
マーカーの情報のアップデート処理は，以下の様になります．

```javascript:既存のマーカーハンドルを取り出して，更新する
markerHandle = markers[drone_name]  // 保存されているマーカー情報をdrone_name(トピック名)をキーにして呼び出す
markerHandle.moveTo([lat,lon], 1000);  // MovingMarkerの移動機能
markerHandle.setRotationAngle(ang);	   // ratatedMarkerの回転機能
markerHandle.setPopupContent( drone_popmessage );	// メッセージ更新
```
`drones["drone/001"]`というデータがあれば，
当然`markers["drone/001"]`というデータもあるので，
まずはマーカー管理ハンドルを取り出します．`markerHandle = markers[drone_name]`

後は，マーカーの位置や向き，ポップアップをアップデートします
1. `moveTo`：MovingMarkerの関数を使って新しい緯度・経度へ移動する．移動には1000ミリ秒かける．
2. `setRotationAngle`：rotatedMarkerの関数を使ってマーカーの向きを変更する．
3. `setPoupuContent`：ポップアップメッセージを新しいものに替える

---
最後に，グローバル変数配列に，{トピック名`drone_name`:ドローン情報`drone_data`}を追加します．
当該トピック名のデータが無い時は新規に作成されます．ある時はデータ更新されます．
なので，メッセージ到着２回目以降はif文は常にfalseになります．

```javascript:グローバル配列dronesにデータを格納する
    }
    drones[drone_name] = drone_data; // ドローンデータ用連想配列の情報を最新のメッセージに更新
```

##Pubプログラムの実行
[dronekitの情報をMQTTで送信してみる](https://qiita.com/hsgucci/items/ec3df139cfa23da4ea0a)
で作った`sitl_mqtt_pub_json.py`を起動して，
ブローカーにメッセージを投げて(Pubして)おきます．

```bash:sitlドローンの位置情報をPubするプログラム
$python sitl_mqtt_pub_json.py
```

##実行結果

Webブラウザのアドレスバーに
`http://localhost/marker_moving_rotated.html`
と入力し，Enterしてページを表示させます．

Pub側プログラムを操作して移動させると，こんな画面が表示さるはずです．
![Screenshot at 2019-05-22 19-24-55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256470/66e2bb12-4902-8da8-15ad-1328da5e3cf0.png)

ちゃんと表示されています．
しかし，実は**<font color="red">Pub側プログラムを終了させても，地図上のマーカーは最後の位置で残り続けてしまいます</font>**．
「終了処理」をやっていませんので．

本来は，
**<font color="blue">一定時間メッセージが来なくなったらマーカーを削除する</font>**
という処理が必要ですね．

このあたりまで来ると，ゲームプログラミング経験のある人は，
「ネットワークゲームの様なマネージメントが必要になるんだな」
と思うでしょう．

・複数ユーザー(ドローン)の接続に対して，異なるアバター(マーカー)を表示する
・急に切断した(LANケーブルぶっこ抜き)ユーザーのアバターを速やかに削除する
・ユーザーの行動ログを取る
・NPC(疑似ドローン)を表示させる

様々な機能が考えられますが，実装が大変ですね．

#おわりに

ドローンのマーカーを地図上にリアルタイムに表示させ，移動や回転できるようにもなりました．
まだまだ機能アップしたい点はありますが，
次回は**MQTTでドローンの操作**をやりたいと思います．