#はじめに
このページは,
の1ページです.
全体を見たい場合は上記ページへお戻りください.
#概要
前回は,Webブラウザからドローンへコマンド送信できるようになりました.
しかし,緯度/経度の指定は小数点を手入力するという方法で面倒極まりないです.
今回は,地図上をクリックしてGOTOの目標地点にする方法を実装します.
- 地図をクリックして座標を取得し,GOTOボタンを押す
- 右クリックメニューを作ってGOTOする
の二種類を試します.
#システム構成
ドローンのシミュレータは前々回使った 「gui_sitl_pubsub.py」 です.
前回作ったWebブラウザ用のプログラム 「map_button.html」 を改変してつくります.
#クリックした座標をエディットボックスへ書き込む
地図上をクリックして,緯度/経度を得る方法については,以下のページが参考になります.
これらのページではmap
という変数名を使っていますが,
今回の記事ではvar mymap = L.map('mapid')〜
としているので読み替えます.
要は,mymap.on('click', function(e) { やらせたい作業 } );
を追加するだけで実現できます.
##プログラム本体
ここ を右クリック-[名前を付けて保存]するか,
以下のコードをコピー&ペーストしてファイルを作成してください.
<!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を付けます.
$ 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.lat
とe.latlng.lng
で読み込んでいます.
(筆者は経度longitudeをlon
と省略していますが,Leafletではlng
です)
こうして得られた緯度・経度をエディットボックスに書き込みます.
document.getElementById('lat').value
は読み込み,書き込みの両方が可能なので,e.latlng.lat
を代入するだけで済みます.
(数値->文字列の変換はJavaScriptが勝手にやってくれている)
##実行結果
Webブラウザのアドレスバーに
http://localhost/map_click.html
と入力し,Enterしてページを表示させます.
以下の画面の様に,地図上の任意の場所をクリックすると,
その場所の緯度と経度がエディットボックスに書き込まれていれば成功です.
後は,GOTOボタンを押せば,任意の場所へドローンを移動させることができますね.
#右クリックメニューで直接GOTOさせる
クリックした場所の緯度/経度を取得して,ドローン操作フォームのエディットボックスへ書き込み,
GOTOボタンを押せば,望みの場所へ移動できるようになりました.
え?**「いちいちGOTOを押すのが面倒臭い」「カーソル移動させるのが大変」**ですか?(^_^;
**「Mission Plannerは地図上で右クリックすると『ここまで飛行』があるじゃん!」**ですと?
確かに...じゃあ右クリックメニューを付けましょう!
余談
ローバー(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/
##プログラム本体
ここ を右クリック-[名前を付けて保存]するか,
以下のコードをコピー&ペーストしてファイルを作成してください.
<!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を付けます.
$ 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してページを表示させます.
以下の画面の様に,
#おわりに
今回は,地図上の任意の位置をクリック/右クリックして目標地点に設定し,
ドローンを移動させることができるようになりました.
次回は実機を動かそうと思っていました.
が,テスト用に作ったUbuntu Linux環境がデスクトップなので,屋外に持ち出せない(泣)
先にVPSや自宅サーバーの話をしようと思います.