Help us understand the problem. What is going on with this article?

地図上のクリックした場所を目標地点にする

はじめに

このページは,

ドローン操作システムを作ろう

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

概要

前回は,Webブラウザからドローンへコマンド送信できるようになりました.
しかし,緯度/経度の指定は小数点を手入力するという方法で面倒極まりないです.

今回は,地図上をクリックしてGOTOの目標地点にする方法を実装します.

  1. 地図をクリックして座標を取得し,GOTOボタンを押す
  2. 右クリックメニューを作ってGOTOする

の二種類を試します.

システム構成

browser_operation.png

ドローンのシミュレータは前々回使った 「gui_sitl_pubsub.py」 です.

前回作ったWebブラウザ用のプログラム 「map_button.html」 を改変してつくります.

クリックした座標をエディットボックスへ書き込む

地図上をクリックして,緯度/経度を得る方法については,以下のページが参考になります.

これらのページではmapという変数名を使っていますが,
今回の記事ではvar mymap = L.map('mapid')〜としているので読み替えます.

要は,mymap.on('click', function(e) { やらせたい作業 } ); を追加するだけで実現できます.

プログラム本体

ここ を右クリック-[名前を付けて保存]するか,
以下のコードをコピー&ペーストしてファイルを作成してください.

map_click.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" /><!-- 文字コードはutf-8を使用する -->
    <title>Map Click Test</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>

    <!-- ログ表示用のスタイルシートなので,これは消さない -->
    <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.6em;
    }
    </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>

<!-- ドローンを操作するボタンを配置 -->
<div>
    <h2>Control</h2>
    <div>
        <form><!-- onclickイベントでOnButtonClick関数を呼んで,ドローンにコマンドをsendする -->
        <input type="button" value="GUIDED" onclick="OnButtonClick('GUIDED');" ><input type="button" value="RTL" onclick="OnButtonClick('RTL');" ><br>
        <input type="button" value="ARM" onclick="OnButtonClick('ARM');" ><input type="button" value="DISARM" onclick="OnButtonClick('DISARM');" ><br>
        <input type="button" value="TAKEOFF" onclick="OnButtonClick('TAKEOFF');" ><input type="button" value="LAND" onclick="OnButtonClick('LAND');" ><br>

        <input type="button" value="GOTO" onclick="OnButtonClick('GOTO');">
        <input type="text" size="10" name="lat" id="lat" placeholder="Latitude" value="35.893246" />
        <input type="text" size="10" name="lon" id="lon" placeholder="Longitude" value="139.954909" />
        <input class='form-control' type="text" size="3" name="alt" id="alt" placeholder="Altitude" value="30" />
        </form>
    </div>
</div>

<!-- <script></script>で囲まれた部分がJavaScript -->
<script>
    // 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に追加する

    // 地図上を左クリックするとエディットボックスにその緯度/経度を書き込む
    mymap.on('click', function(e) {
        document.getElementById('lat').value = e.latlng.lat;
        document.getElementById('lon').value = e.latlng.lng;
    } );

    // マーカーにする画像を読み込む
    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] });

    // Log表示用の関数部分
    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');

    // MQTT over WebSocketの初期化
    var wsbroker = location.hostname;   // MQTTブローカーは自分自身
    var wsport = 15675; // MQTTの標準ポート番号は1883だが,WebSocketは15675とした(RabbitMQと同じ仕様)

    // MQTTのクライアントを作成する クライアントID名はランダムに作る
    var client = new Paho.MQTT.Client(wsbroker, wsport, "/ws", "myclientid_" + parseInt(Math.random() * 100, 10));

    //== ここからボタンを押した時の処理=============================================
    // ドローン操作用のコマンド
    var command = {
                "command":"None",
                "d_lat":"0",
                "d_lon":"0",
                "d_alt":"0",
    }

    // HTML上のボタンが押された時の処理
    function OnButtonClick(str) {
        command["command"] = str;   // 引数の文字列がそのままコマンドになる

        // GOTOのときは,緯度/経度/高度も取得してコマンドを作る
        if( str == "GOTO" ) {
            lat = document.getElementById("lat").value;
            lon = document.getElementById("lon").value;
            alt = document.getElementById("alt").value;
            command["d_lat"] = lat;
            command["d_lon"] = lon;
            command["d_alt"] = alt;
        }

        // JSON型にしてからMQTTでPublishする
        json_command = JSON.stringify(command);     // JSON型にする
        message = new Paho.MQTT.Message(json_command);      // MQTTのメッセージパケットを作る
        message.destinationName = "ctrl/001";   // トピック名を設定
        client.send(message);   // MQTTでPubする
        debug("SEND ON " + message.destinationName + " PAYLOAD " + message.payloadString);   //debugボックスに表示
    }
    //=======================ボタンを押した時の処理 ここまで=============

    // 切断時のコールバック
    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; // ドローンデータ用連想配列の情報を最新のメッセージに更新

    };

    // MQTTの接続オプション
    var options = {
        timeout: 3,
        onSuccess: function () {
            debug("CONNECTION SUCCESS");
            client.subscribe('drone/#', {qos: 1});
        },
        onFailure: function (message) {
            debug("CONNECTION FAILURE - " + message.errorMessage);
        }
    };

    // サーバーがHTTPS対応だった時の処理
    if (location.protocol == "https:") {
        options.useSSL = true;
    }

    // 最初にメッセージを表示してMQTTをブローカーに接続
    debug("CONNECT TO " + wsbroker + ":" + wsport);
    client.connect(options);        // 接続
</script>

</body>
</html>

保存したmap_click.htmlを,Webサーバーが利用するフォルダへコピーします.
管理者権限が必要なのでsudoを付けます.

apacheのフォルダへコピー
$ sudo cp map_click.html /var/www/html/

プログラム解説

前回の map_button.html から大きく変化した部分だけ解説します.

script部

変化した部分は93行目のこれだけです.

地図クリックイベントの処理
    // 地図上を左クリックするとエディットボックスにその緯度/経度を書き込む
    mymap.on('click', function(e) {
        document.getElementById('lat').value = e.latlng.lat;
        document.getElementById('lon').value = e.latlng.lng;
    } );

mymapに「click」のイベントが発生した際に実行すべきコールバック関数を登録しています.
イベント関数はeという引数を持っており,イベント発生時の緯度・経度も入っています.
e.latlng.late.latlng.lngで読み込んでいます.
(筆者は経度longitudeをlonと省略していますが,Leafletではlngです)

こうして得られた緯度・経度をエディットボックスに書き込みます.

document.getElementById('lat').valueは読み込み,書き込みの両方が可能なので,e.latlng.latを代入するだけで済みます.
(数値->文字列の変換はJavaScriptが勝手にやってくれている)

実行結果

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

以下の画面の様に,地図上の任意の場所をクリックすると,
その場所の緯度と経度がエディットボックスに書き込まれていれば成功です.
map_click.png
後は,GOTOボタンを押せば,任意の場所へドローンを移動させることができますね.

右クリックメニューで直接GOTOさせる

クリックした場所の緯度/経度を取得して,ドローン操作フォームのエディットボックスへ書き込み,
GOTOボタンを押せば,望みの場所へ移動できるようになりました.

え?「いちいちGOTOを押すのが面倒臭い」「カーソル移動させるのが大変」ですか?(^_^;
「Mission Plannerは地図上で右クリックすると『ここまで飛行』があるじゃん!」ですと?
確かに...じゃあ右クリックメニューを付けましょう!
flyhere.png
余談
ローバー(ArduRover)や水中ロボット(ArduSub)でも「ここまで飛行」になるのは変ですよね.
というのは,元々の英語が「Fly To Here」だからです.
Flyじゃないドローンもあるのに.
服部さんと日本語化した際には直訳でやったので,気づかなかったんですよ(言い訳してみる)
日本語版だけでも,「ここへ移動」とかに修正してPull Requestする方が良いかもしれません...

プラグインの追加

Leafletには,右クリックメニューを拡張するプラグインがあります.

Leaflet.contextmenu  https://github.com/aratcliffe/Leaflet.contextmenu
   スタイルファイル を右クリック-[名前を付けて保存]
   JavaScriptファイル を右クリック-[名前を付けて保存]

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

プラグインをコピーしておく
$ sudo cp leaflet.contextmenu.css /var/www/html/
$ sudo cp leaflet.contextmenu.js /var/www/html/

プログラム本体

ここ を右クリック-[名前を付けて保存]するか,
以下のコードをコピー&ペーストしてファイルを作成してください.

map_r_click.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" /><!-- 文字コードはutf-8を使用する -->
    <title>Right Click Menu</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>

    <!-- 右クリックメニューのプラグインを読み込む -->
    <link rel="stylesheet" href="leaflet.contextmenu.css" />
    <script src="leaflet.contextmenu.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>

    <!-- ログ表示用のスタイルシートなので,これは消さない -->
    <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.6em;
    }
    </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>

<!-- ドローンを操作するボタンを配置 -->
<div>
    <h2>Control</h2>
    <div>
        <form><!-- onclickイベントでOnButtonClick関数を呼んで,ドローンにコマンドをsendする -->
        <input type="button" value="GUIDED" onclick="OnButtonClick('GUIDED');" ><input type="button" value="RTL" onclick="OnButtonClick('RTL');" ><br>
        <input type="button" value="ARM" onclick="OnButtonClick('ARM');" ><input type="button" value="DISARM" onclick="OnButtonClick('DISARM');" ><br>
        <input type="button" value="TAKEOFF" onclick="OnButtonClick('TAKEOFF');" ><input type="button" value="LAND" onclick="OnButtonClick('LAND');" ><br>

        <input type="button" value="GOTO" onclick="OnButtonClick('GOTO');">
        <input type="text" size="10" name="lat" id="lat" placeholder="Latitude" value="35.893246" />
        <input type="text" size="10" name="lon" id="lon" placeholder="Longitude" value="139.954909" />
        <input type="text" size="3" name="alt" id="alt" placeholder="Altitude" value="30" />
        </form>
    </div>
</div>

<!-- <script></script>で囲まれた部分がJavaScript -->
<script>
    // mapidと名の付いたdiv要素に地図を作成し,視点は柏の葉キャンパス駅前付近,ズームレベルは16に設定
    var mymap = L.map('mapid',{
        // 右クリックメニューの登録
        contextmenu: true,
        contextmenuItems: [{
            text: 'ここへGOTO',
            callback: moveHere      // コールバック関数を登録
        }]  
    });
    mymap.setView([35.894087,139.952447], 17);

    function moveHere(e) {
        // イベント引数 e から緯度/経度を得てコマンドを作る
        command["command"] = "GOTO";    
        command["d_lat"] = e.latlng.lat;
        command["d_lon"] = e.latlng.lng;
        command["d_alt"] = document.getElementById("alt").value;

        // ついでなのでエディットボックスの中身も更新しておく
        document.getElementById("lat").value = e.latlng.lat;
        document.getElementById("lon").value = e.latlng.lng;

        // JSON型にしてからMQTTでPublishする
        json_command = JSON.stringify(command);     // JSON型にする
        message = new Paho.MQTT.Message(json_command);      // MQTTのメッセージパケットを作る
        message.destinationName = "ctrl/001";   // トピック名を設定
        client.send(message);   // MQTTでPubする
        debug("SEND ON " + message.destinationName + " PAYLOAD " + message.payloadString);   //debugボックスに表示
    }


    // 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に追加する

    // 地図上を左クリックするとエディットボックスにその緯度/経度を書き込む
    mymap.on('click', function(e) {
        document.getElementById('lat').value = e.latlng.lat;
        document.getElementById('lon').value = e.latlng.lng;
    } );

    // マーカーにする画像を読み込む
    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] });

    // Log表示用の関数部分
    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');

    // MQTT over WebSocketの初期化
    var wsbroker = location.hostname;   // MQTTブローカーは自分自身
    var wsport = 15675; // MQTTの標準ポート番号は1883だが,WebSocketは15675とした(RabbitMQと同じ仕様)

    // MQTTのクライアントを作成する クライアントID名はランダムに作る
    var client = new Paho.MQTT.Client(wsbroker, wsport, "/ws", "myclientid_" + parseInt(Math.random() * 100, 10));

    //== ここからボタンを押した時の処理=============================================
    // ドローン操作用のコマンド
    var command = {
                "command":"None",
                "d_lat":"0",
                "d_lon":"0",
                "d_alt":"0",
    }

    // HTML上のボタンが押された時の処理
    function OnButtonClick(str) {
        command["command"] = str;   // 引数の文字列がそのままコマンドになる

        // GOTOのときは,緯度/経度/高度も取得してコマンドを作る
        if( str == "GOTO" ) {
            lat = document.getElementById("lat").value;
            lon = document.getElementById("lon").value;
            alt = document.getElementById("alt").value;
            command["d_lat"] = lat;
            command["d_lon"] = lon;
            command["d_alt"] = alt;
        }

        // JSON型にしてからMQTTでPublishする
        json_command = JSON.stringify(command);     // JSON型にする
        message = new Paho.MQTT.Message(json_command);      // MQTTのメッセージパケットを作る
        message.destinationName = "ctrl/001";   // トピック名を設定
        client.send(message);   // MQTTでPubする
        debug("SEND ON " + message.destinationName + " PAYLOAD " + message.payloadString);   //debugボックスに表示
    }
    //=======================ボタンを押した時の処理 ここまで=============

    // 切断時のコールバック
    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; // ドローンデータ用連想配列の情報を最新のメッセージに更新

    };

    // MQTTの接続オプション
    var options = {
        timeout: 3,
        onSuccess: function () {
            debug("CONNECTION SUCCESS");
            client.subscribe('drone/#', {qos: 1});
        },
        onFailure: function (message) {
            debug("CONNECTION FAILURE - " + message.errorMessage);
        }
    };

    // サーバーがHTTPS対応だった時の処理
    if (location.protocol == "https:") {
        options.useSSL = true;
    }

    // 最初にメッセージを表示してMQTTをブローカーに接続
    debug("CONNECT TO " + wsbroker + ":" + wsport);
    client.connect(options);        // 接続
</script>

</body>
</html>

保存したmap_r_click.htmlを,Webサーバーが利用するフォルダへコピーします.
管理者権限が必要なのでsudoを付けます.

apacheのフォルダへコピー
$ sudo cp map_r_click.html /var/www/html/

プログラム解説

先程の map_click.html から大きく変化した部分だけ解説します.

head部

右クリックメニュー(コンテキストメニュー)のプラグインを読み込んでいます.

右クリックメニューのプラグイン
<link rel="stylesheet" href="leaflet.contextmenu.css" />
<script src="leaflet.contextmenu.js" type="text/javascript"></script>

スタイルシートのファイル(.css)は<link>タグで,
JavaScriptのファイル(.js)は<script>タグで読み込むのは,
Leafletのファイルと同じ使い方です.

script部

変更は一箇所だけです.
<script>の冒頭でマップ本体を定義していた
var maymap = L.map('mapid')にオプションを追加します.
Leafletでは引数が増えて中括弧{ }で追加します.
オプションは2つです.
・右クリックメニューを有効にするcontextmenu: ture
・メニューに表示される文字と呼び出す関数
 contextmenuItems: [{ text: 'ここへGOTO', callback: moveHere }]
これで右クリックが有効になり,作ったメニューが表示されます.
地図の視点を移動させるsetView関数はメソッドチェーンせずに,別に書きました.

コンテキストメニューの追加とコールバック関数
    var mymap = L.map('mapid',{
        // 右クリックメニューの登録
        contextmenu: true,
        contextmenuItems: [{
            text: 'ここへGOTO',
            callback: moveHere      // コールバック関数を登録
        }]  
    });
    mymap.setView([35.894087,139.952447], 17);

    function moveHere(e) {
        // イベント引数 e から緯度/経度を得てコマンドを作る
        command["command"] = "GOTO";    
        command["d_lat"] = e.latlng.lat;
        command["d_lon"] = e.latlng.lng;
        command["d_alt"] = document.getElementById("alt").value;

        // ついでなのでエディットボックスの中身も更新しておく
        document.getElementById("lat").value = e.latlng.lat;
        document.getElementById("lon").value = e.latlng.lng;

        // JSON型にしてからMQTTでPublishする
        json_command = JSON.stringify(command);     // JSON型にする
        message = new Paho.MQTT.Message(json_command);      // MQTTのメッセージパケットを作る
        message.destinationName = "ctrl/001";   // トピック名を設定
        client.send(message);   // MQTTでPubする
        debug("SEND ON " + message.destinationName + " PAYLOAD " + message.payloadString);   //debugボックスに表示
    }

moveHere関数では,
右クリックした場所の緯度/経度,エディットボックス内の高度の3つの情報を元に,
JSONメッセージを作ってMQTTで送信しています.

実行結果

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

以下の画面の様に,
1. 地図上の任意の場所を右クリックすると,「ここへGOTO」と表示されます.
2. それをクリックするとコマンド送信され,ドローンが移動します.
Screenshot at 2019-06-04 10-39-28.png

おわりに

今回は,地図上の任意の位置をクリック/右クリックして目標地点に設定し,
ドローンを移動させることができるようになりました.

次回は実機を動かそうと思っていました.
が,テスト用に作ったUbuntu Linux環境がデスクトップなので,屋外に持ち出せない(泣)
先にVPSや自宅サーバーの話をしようと思います.

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away