word で、本来 "1. 序論 1.1 背景 現在、日本の多くの行政区において...... "と表示されるところが、"1."、"1.1 "が表示されなくなりました。 ナビゲーションだけでなく、本文にも表示されていません
見出し1, 見出し2、が全部表示されなくなったが、これで直った。
心臓に悪いから、こういうのは止めて欲しい。
江端智一のホームページ
見出し1, 見出し2、が全部表示されなくなったが、これで直った。
心臓に悪いから、こういうのは止めて欲しい。
以前、
に記載しましたが、SDカードのサイズがほんの少しだけ小さいだけ、という理由で、ラズパイのSDカードのクローンを作るのに失敗したお話をしました。
で、あの時は、SDカード→SDカードの場合に成功したのですが、今回は、ハードディスクに格納したimgファイル→SDカードのケースで対応できませんでした。
.img
ファイルは「SDカード全体の構造」を再現する形式なので、たとえ未使用部分があってもその全体サイズ分を丸ごと書き込む必要があります。そのため、数百MBでも足りないと完全に書き込めません。
そこで、今回は、この対応を試みて、1日がかりで成功しました。128GBのimgファイルのコピーって、もの凄く時間がかかる(2時間以上)ので、そっちでも苦労しました。
が、この方法で、なんとか成功できたようなので、自分用にメモを残しておきます(抜粋のみ)。
img
ファイルを縮小する(上級者向け)pishrink.sh
を使うこれにより、未使用領域を削除して圧縮済み .img
を作成できます。これで書き込み先のSDカードがそれよりわずかに小さい場合にも書き込めるようになります。
※ Windows単体では難しいですが、WSLやUbuntu仮想マシンがあるなら対応可能です。
私は、WindowsのHDDにimgファイルもってきて、WSLから実施しました。
ただ、pishrink.sh
はダウンロードする必要があります。https://github.com/Drewsif/PiShrink/blob/master/pishrink.sh
また、その途中でparted
のインストールも必要になりました。
sudo apt update
sudo apt install parted
こうして、
sudo ./pishrink.sh shajyou_6001_20250221.img shajyou_6001_20250221_shrink.img
を実施しました
で、その結果以下のようなものが作成されました。
約1/3に圧縮されていますので、128GBのSDカードには軽く入る計算になります。
さて、次に、SDカードへの書き込み(クローンの作成)に入ります
Raspberry Pi Imager をダウンロード・インストール
→ 公式サイト: https://www.raspberrypi.com/software/
SDカードをPCに挿入
Raspberry Pi Imagerを起動し、以下を選択:
「OSを選ぶ」→ 一番下の「カスタムイメージを使う」→ shajyou_6001_20250221_shrink.img
を選択
「ストレージを選ぶ」→ SDカードを選択
「書き込む」をクリック → 書き込み完了まで待つ
書き込み→検証で1時間くらいはかかるので、諦めて待ってください。
-----
最後に、このクローンのSDカードで、ラズパイが立ち上がるのかを確認するのですが、かなりドキドキします。
私の場合、初回のブート時に、
てな画面がでてきて、自動的に再起動がかかりましたが、2度目の起動でログイン画面になり、ホッとしました。
最初は、USB関連を抜いて追いた方が良いかもしれません(キーボードとマウスはしょうがないとしても)。
以上
P.S. さて、これからあちこち(PCやらNASやら)にコピーしたimgファイルの削除です。imgファイルだけで128GBの容量を取られるので、本当にシャレになりません。
GStreamerをC言語でフレームレートの設定ができなかったので、色々試していましたが、
caps = gst_caps_from_string("video/x-raw, width=640, height=360,framerate=5/1")は動きません(少なくとも私のコードでは駄目だった)。
これね、GStreamerのコマンドと同じ内容になるように並べなければあらないようです。
gst-launch-1.0 rtspsrc location=rtsp://cam:Cam12345@192.168.0.10/Src/MediaInput/stream_1 latency=0 ! rtph264depay ! avdec_h264 ! videoconvert ! videoscale ! video/x-raw,width=640,height=360 ! videorate ! video/x-raw,framerate=1/1 ! x264enc bitrate=1700 speed-preset=ultrafast tune=zerolatency key-int-max=1 ! mpegtsmux ! srtserversink uri = "srt://192.168.101.10:38091" latency=500
これを忠実にコーディングする必要があるようです。
で、今のコードに入っていなかったのが"video/x-raw,framerate=1/1" です。
なので、capsfilter2 やら caps2 のコードを追加する必要がありました。
という訳で、以下のような追加をすることで動くようになりました。
ーーーーー
GstElement *videoscale, *capsfilter, *videorate, *x264enc, *mpegtsmux, *srtserversink;
GstElement *capsfilter2;
GstCaps *caps;
GstCaps *caps2;
ーーーーー
capsfilter = gst_element_factory_make("capsfilter", "capsfilter");
videorate = gst_element_factory_make("videorate", "videorate");
capsfilter2 = gst_element_factory_make("capsfilter", "capsfilter2");
ーーーーー
if (!pipeline || !rtspsrc || !rtph264depay || !avdec_h264 || !videoconvert || !videoscale ||
!capsfilter || !videorate || !capsfilter2 || !x264enc || !mpegtsmux || !srtserversink) {
g_printerr("Not all elements could be created.\n");
abc_log_message("Not all elements could be created.");
return -1;
}
ーーーーー
caps = gst_caps_from_string("video/x-raw,width=640,height=360");
g_object_set(capsfilter, "caps", caps, NULL);
gst_caps_unref(caps);
caps2 = gst_caps_from_string("video/x-raw,framerate=10/1");
g_object_set(capsfilter2, "caps", caps2, NULL);
gst_caps_unref(caps2);
ーーーーー
gst_bin_add_many(GST_BIN(pipeline), rtspsrc, rtph264depay, avdec_h264, videoconvert, videoscale,
capsfilter, videorate, capsfilter2, x264enc, mpegtsmux, srtserversink, NULL);
if (!gst_element_link_many(rtph264depay, avdec_h264, videoconvert, videoscale, capsfilter, videorate,
capsfilter2, x264enc, mpegtsmux, srtserversink, NULL)) {
g_printerr("Elements could not be linked.\n");
abc_log_message("Elements could not be linked.");
gst_object_unref(pipeline);
return -1;
}
ーーーーー
もっと簡単にできると思ったんだけどなぁ・・・結構、手間かかったなぁ。
一つ一つが、大変納得した内容だったので、私のメモ用に記録しておきます。
Go言語を用いたマルチエージェントシミュレーション(MAS)の利点と欠点について、他の言語(C++, Python, Juliaなど)と比較しながら論じる。
Goはgoroutineを用いた並行処理が特徴であり、エージェントごとにgoroutineを割り当てることで効率的な並列処理が可能である。他の言語ではスレッド管理(C++のstd::threadやPythonのthreading)が必要になるが、Goではシンプルな記述で実装できる。
例:
このようにGoの並行処理はシンプルかつ軽量であり、大量のエージェントを扱うシミュレーションに適している。
Goはシンプルな文法と強力な標準ライブラリを持つため、コードの可読性が高く、開発者間での共有が容易である。
C++ではテンプレートやマルチスレッド処理が複雑になりやすく、PythonではGIL(Global Interpreter Lock)の影響で並列処理が制限される。一方、GoはGILの問題がなく、コードの記述量も比較的少ないため、長期的な開発に向いている。
Goはコンパイルが非常に速く、バイナリを直接実行できるため、デバッグやテストのサイクルを短縮できる。C++のように長時間のコンパイルが不要で、Pythonのようなインタープリタ型の遅延もない。
Goは静的バイナリを生成できるため、Windows, Linux, macOS などの異なる環境での実行が容易である。C++ではコンパイル時にライブラリ依存の問題が生じやすく、Pythonでは環境設定(仮想環境やパッケージ管理)が面倒になりがちだが、Goでは1つのバイナリで解決できる。
GoはもともとGoogleが開発した言語であり、クラウド環境(GCP, AWS)やWebとの親和性が高い。
シミュレーション結果をWeb API経由でリアルタイム表示したり、分散処理の一部をクラウド上で実行する際に、Goの標準ライブラリだけでHTTPサーバを簡単に実装できるのは大きなメリット。
PythonやC++には、最適化された数値計算ライブラリ(NumPy, SciPy, Eigen, OpenMPなど)が豊富にあるのに対し、Goは数値計算や線形代数のライブラリが少ない。
そのため、大量の行列演算や物理シミュレーションを行う場合、Go単体では計算効率が劣る可能性がある。
対策:
gonum
ライブラリを使用する。C++はCUDAやOpenCLを利用してGPUによる並列計算が可能であり、PythonもTensorFlowやCuPyを通じてGPUを活用できる。しかし、Goは公式にはGPUを直接サポートしていないため、CUDAを使う場合はCGO経由でC++のライブラリを呼び出す必要がある。
対策:
gorgonia
(Go向けのニューラルネットワークライブラリ)を利用すると、一部のGPU計算が可能。PythonやC++は、数多くの学術論文や研究事例があり、豊富なサンプルコードが存在する。一方で、Goは学術分野ではあまり使われておらず、論文や研究事例が少ない。
特に、MAS関連のライブラリやフレームワークが未成熟なため、開発者が独自に実装する必要がある。
対策:
Goのガベージコレクション(GC)は自動的にメモリを管理するが、大規模なシミュレーションではGCの影響で予期しないタイミングで一時的な遅延が発生する可能性がある。
C++のように明示的なメモリ管理ができないため、リアルタイム性を求めるシミュレーションでは不利になることがある。
対策:
sync.Pool
を活用してメモリ管理のオーバーヘッドを減らす。Go言語を使ったMASは、並行処理の容易さ・コードのシンプルさ・クラウドとの親和性に優れており、大規模エージェントシミュレーションの実装がしやすい。
しかし、GPUサポートの弱さ・計算ライブラリの不足・研究事例の少なさといった課題もあり、特に高度な数値計算が必要なシミュレーションではPythonやC++の方が適している。
Go言語が適しているケース:
PythonやC++の方が適しているケース:
最適なアプローチ:
Go単体でMASを実装するよりも、計算部分をPython/C++で処理し、Goは並列制御やネットワーク管理に使うというハイブリッドアプローチが有効となる。
昨日、
について記載したんだけど、これ簡単に言うと『goroutineを止めて、サブルーチンにした』というものです。
300万のエージェントが、メインルーチンの情報変更に上手く連動しなくなると、"goroutineの自己破壊命令が届かない"という問題がボロボロでてきたからです。
まあ、それでも、『数十万人分、361万行の移動データを、全部表示して正常終了した』というGo言語の凄さには、相変らず感心しているんですけどね(昨夜、PC回しながら寝たのですが、今朝、全部のオブジェクトが消えて、正常終了していました)。
起動方法
$go run server22-1.go
↓
http://localhost:8080
↓
$go run pm_proxy_single_socket.go
// server22-1.go
/*
このプログラムは、WebSocket を利用したリアルタイム位置情報サーバーである。
- クライアントから送信された位置情報を受信し、処理後にレスポンスを返す。
- 位置情報はチャネルを介して他のクライアントと共有される。
- `sync.Mutex` を使用してスレッド安全な処理を実現。
- WebSocket の接続エラー時には適切にログを出力し、安定した通信を確保。
- HTML を提供し、ブラウザ上で WebSocket 通信を確認可能。
*/
package main
import (
"flag"
"html/template"
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
// GetLoc GetLoc
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
//Address string `json:"address"`
}
// var addr = flag.String("addr", "localhost:8080", "http service address")
var addr = flag.String("addr", "0.0.0.0:8080", "http service address") // テスト
var upgrader = websocket.Upgrader{} // use default options
var chan2_1 = make(chan GetLoc)
var maxid = 0
var mutex sync.Mutex
func echo2(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
//mutex := new(sync.Mutex)
for {
//mt, message, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
//_, _, err := c.ReadMessage() // クライアントからのメッセージの受信(mtはクライアント識別子)
//mutex.Lock() // ここに置くとデッドロックしてしまう
gl := new(GetLoc)
err := c.ReadJSON(&gl) // クライアントからのメッセージの受信
mutex.Lock()
// 原因不明の対処処理
if gl.ID == 0 && gl.Lat < 0.01 && gl.Lng < 0.01 {
mutex.Unlock()
break
} else if gl.ID < -1 { // 受理できないメッセージとして返信する
//条件分岐 (変なIDが付与されているメッセージは潰す)
//if (gl.ID > maxid) || (gl.ID < -1) { // 受理できないメッセージとして返信する
gl.ID = -1
gl.Lat = -999
gl.Lng = -999
err2 := c.WriteJSON(gl)
if err2 != nil {
log.Println("write1:", err2)
mutex.Unlock()
break
}
} else { // それ以外は転送する
/*
log.Printf("echo2 after c.WriteJSON(gl) ID:%d", gl.ID)
log.Printf("echo2 after c.WriteJSON(gl) Lat:%f", gl.Lat)
log.Printf("echo2 after c.WriteJSON(gl) Lng:%f", gl.Lng)
*/
if err != nil {
log.Println("read:", err)
mutex.Unlock()
break
}
//fmt.Printf("echo2 before chan2_1 <- *gl\n")
chan2_1 <- *gl
//fmt.Printf("echo2 after chan2_1 <- *gl\n")
//で、ここで受けとる
//gl2 := new(GetLoc)
//fmt.Printf("echo2 before gl2 := <-chan2_1\n")
gl2 := <-chan2_1
maxid = gl2.ID // ID最大値の更新
/*
log.Printf("echo2 after gl2 := <-chan2_1 ID:%d", gl2.ID)
log.Printf("echo2 after gl2 := <-chan2_1 Lat:%f", gl2.Lat)
log.Printf("echo2 after gl2 := <-chan2_1 Lng:%f", gl2.Lng)
*/
//fmt.Printf("echo2 before err2 := c.WriteJSON(gl2)\n")
err2 := c.WriteJSON(gl2)
//fmt.Printf("echo2 after err2 := c.WriteJSON(gl2)\n")
if err2 != nil {
log.Println("write2:", err2)
mutex.Unlock()
break
}
//fmt.Printf("end of echo2\n")
}
mutex.Unlock()
}
}
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
/* ここでロックして待つ */
for {
//fmt.Printf("echo before gl := <-chan2_1\n")
gl := <-chan2_1
//fmt.Printf("echo after gl := <-chan2_1\n")
//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.id = %d\n", gl.ID)
//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lat = %f\n", gl.Lat)
//fmt.Printf("echo before err = c.WriteJSON(gl) gl2.lng= %f\n", gl.Lng)
err = c.WriteJSON(gl)
if err != nil {
log.Println("WriteJSON1:", err)
}
//fmt.Printf("echo after err = c.WriteJSON(gl)\n")
//fmt.Printf("echo before err = c.RreadJSON(gl)\n")
gl2 := new(GetLoc)
err2 := c.ReadJSON(&gl2)
//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.id = %d\n", gl2.ID)
//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lat = %f\n", gl2.Lat)
//fmt.Printf("echo after err = c.ReadJSON(&gl2) gl2.lng= %f\n", gl2.Lng)
if err2 != nil {
log.Println("ReadJSON:", err2)
}
// ここからチャネルで返す
//fmt.Printf("echo before chan2_1 <- *gl2 gl2.id = %d\n", gl2.ID)
//fmt.Printf("echo before chan2_1 <- *gl2 gl2.lat = %f\n", gl2.Lat)
//fmt.Printf("echo before chan2_1 <- *gl2 gl2.lng = %f\n", gl2.Lng)
chan2_1 <- *gl2
//fmt.Printf("echo after chan2_1 <- *gl2\n")
//fmt.Printf("end of echo\n")
}
}
func home(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
}
func smartphone(w http.ResponseWriter, r *http.Request) {
smartphoneTemplate.Execute(w, "ws://"+r.Host+"/echo2")
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/echo2", echo2) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/echo", echo) // echo関数を登録 (サーバとして必要)
http.HandleFunc("/", home) // home関数を登録
http.HandleFunc("/smartphone", smartphone) // smartphone関数を登録
log.Fatal(http.ListenAndServe(*addr, nil)) // localhost:8080で起動をセット
}
var smartphoneTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script>
function obj(id, lat, lng){
this.id = id;
this.lat = lat;
this.lng = lng;
}
function random(min, max){
return Math.random()*(max-min) + min;
}
// var personal_id;
var lat = 35.654543;
var lng = 139.795534;
window.addEventListener("load", function(evt) {
var output = document.getElementById("output");
var input = document.getElementById("input");
var ws;
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
var personal_id = 0;
///// 起動時のボタン
// disabled属性を削除
document.getElementById("open").removeAttribute("disabled");
document.getElementById("open").style.color = "black";
// disabled属性を設定 (closeボタンを非活性化)
document.getElementById("close").setAttribute("disabled", true);
document.getElementById("close").style.color = "White";
// disabled属性を設定 (sendボタンを非活性化)
document.getElementById("send").setAttribute("disabled", true);
document.getElementById("send").style.color = "White";
document.getElementById("open").onclick = function(evt) {
console.log("document.getElementById open");
// disabled属性を設定 (openボタンを非活性化)
document.getElementById("open").setAttribute("disabled", true);
document.getElementById("open").style.color = "White";
// disabled属性を削除
document.getElementById("send").removeAttribute("disabled");
document.getElementById("send").style.color = "black";
// disabled属性を削除
document.getElementById("close").removeAttribute("disabled");
document.getElementById("close").style.color = "black";
////////// 削除2
// ws = new WebSocket("{{.}}");
////////// 削除2終り
////////// 削除1
//var send_obj = new obj(0, 35.654543,139.795534); // 最初は"0"でエントリ
//console.log("open:send_obj");
//console.log(send_obj.id);
//console.log(send_obj.lat);
//console.log(send_obj.lng);
//var json_obj = JSON.stringify(send_obj);
//ws.send(json_obj);
/////////// 削除1終り
if (ws) {
return false;
}
////////// 追加2
ws = new WebSocket("{{.}}");
////////// 追加2終り
ws.onopen = function(evt) {
print("OPEN");
//ws = new WebSocket("{{.}}");
////////// 追加1
var send_obj = new obj(0, 35.654543,139.795534); // 最初は"0"でエントリ
console.log("open:send_obj");
console.log(send_obj.id);
console.log(send_obj.lat);
console.log(send_obj.lng);
var json_obj = JSON.stringify(send_obj);
ws.send(json_obj);
/////////// 追加1終り
}
ws.onclose = function(evt) {
print("CLOSE");
ws = null;
}
ws.onmessage = function(evt) { // 受信したメッセージはここに飛んでくる
print("RESPONSE: " + evt.data); // jsonメッセージの内容を表示
// データをJSON形式に変更
var obj = JSON.parse(evt.data);
personal_id = obj.id; // IDの取得(何回も取る必要はないが)
console.log("personal_id");
console.log(personal_id);
if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
console.log("before ws.close()");
ws.close();
console.log("after ws.close()");
}
}
ws.onerror = function(evt) {
print("ERROR: " + evt.data);
}
return false;
};
document.getElementById("send").onclick = function(evt) {
console.log("document.getElementById send");
// disabled属性を設定 (openボタンを非活性化)
document.getElementById("open").setAttribute("disabled", true);
document.getElementById("open").style.color = "White";
// disabled属性を削除
document.getElementById("send").removeAttribute("disabled");
document.getElementById("send").style.color = "black";
// disabled属性を削除
document.getElementById("close").removeAttribute("disabled");
document.getElementById("close").style.color = "black";
if (!ws) {
console.log("return false send");
return false;
}
lat += random(0.5, -0.5) * 0.00001 * 10 * 5;
lng += random(0.5, -0.5) * 0.00002 * 10 * 5
//var send_obj = new obj(personal_id, 35.654543,139.795534); // idでエントリ
var send_obj = new obj(personal_id, lat, lng); // idでエントリ
console.log("send:send_obj");
console.log(send_obj.id);
console.log(send_obj.lat);
console.log(send_obj.lng);
var json_obj = JSON.stringify(send_obj);
ws.send(json_obj);
/*
print("SEND: " + input.value);
ws.send(input.value);
return false;
*/
return false;
};
document.getElementById("close").onclick = function(evt) {
console.log(" document.getElementById close");
// disabled属性を削除
document.getElementById("open").removeAttribute("disabled");
document.getElementById("open").style.color = "black";
// disabled属性を設定 (closeボタンを非活性化)
document.getElementById("close").setAttribute("disabled", true);
document.getElementById("close").style.color = "White";
// disabled属性を設定 (sendボタンを非活性化)
document.getElementById("send").setAttribute("disabled", true);
document.getElementById("send").style.color = "White";
if (!ws) {
return false;
}
var send_obj = new obj(personal_id, 999.9, 999.9); // 最初は"0"でエントリ
console.log("close:send_obj");
console.log(send_obj.id);
console.log(send_obj.lat);
console.log(send_obj.lng);
var json_obj = JSON.stringify(send_obj);
ws.send(json_obj);
//ws.close(); // これはws.onmessageの方で実施
return false;
};
});
</script>
</head>
<body>
<table>
<tr><td valign="top" width="50%">
<p>Click "Open" to create a connection to the server,
"Send" to send a message to the server and "Close" to close the connection.
You can change the message and send multiple times.
<p>
<form>
<button id="open">Open</button>
<!-- <p><input id="input" type="text" value="Hello world!"> -->
<button id="send">Send</button>
<button id="close">Close</button>
</form>
</td><td valign="top" width="50%">
<div id="output"></div>
</td></tr></table>
</body>
</html>
`))
var homeTemplate = template.Must(template.New("").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>PruneMobile</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.0.0-beta.2.rc.2/leaflet.js"></script>
<script src="http://kobore.net/PruneCluster.js"></script> <!-- これ、いずれローカルホストから取れるように換える -->
<link rel="stylesheet" href="http://kobore.net/examples.css"/> <!-- これも、いずれローカルホストから取れるように換える -->
<!-- goのテンプレートのローカルって、どこになるんだろう? -->
</head>
<body>
<div id="map"></div>
<script>
ws = new WebSocket("{{.}}"); // websocketの確立
/*
var print = function(message) {
var d = document.createElement("div");
d.textContent = message;
output.appendChild(d);
};
*/
// 引数にはミリ秒を指定。(例:5秒の場合は5000)
function sleep(a){
var dt1 = new Date().getTime();
var dt2 = new Date().getTime();
while (dt2 < dt1 + a){
dt2 = new Date().getTime();
}
return;
}
var map = L.map("map", {
attributionControl: false,
zoomControl: false
}).setView(new L.LatLng(35.36716428833585, 139.62724520774287), 16); // 富岡
//}).setView(new L.LatLng(33.58973407765046, 130.41048227121925), 16); // 中州
//}).setView(new L.LatLng(35.654543, 139.795534), 18); 豊洲
// }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
detectRetina: true,
maxNativeZoom: 18
}).addTo(map);
var leafletView = new PruneClusterForLeaflet(1,1); // (120,20)がデフォルト
ws.onopen = function (event) {
}
var markers = [];
//var helicopterIcon = L.icon({ iconUrl: 'http://sintef-9012.github.io/PruneCluster/examples/helicopter.png', iconSize: [48, 48] });
//var airplaneIcon = L.icon({ iconUrl: 'http://sintef-9012.github.io/PruneCluster/examples/airplane.png', iconSize: [48, 48] });
// 受信すると、勝手にここに飛んでくる
ws.onmessage = function (event) {
// データをJSON形式に変更
var obj = JSON.parse(event.data);
console.log("233");
console.log(obj.id);
console.log(obj.lat);
console.log(obj.lng);
if (obj.id == 0){ // idが未登録の場合
console.log("obj.id == 0")
// データをマーカーとして登録
var marker = new PruneCluster.Marker(obj.lat, obj.lng);
// 参考資料 http://embed.plnkr.co/WmtpkEqSDJFuFeuiYP54/
//var marker = new PruneCluster.Marker(obj.lat, obj.lng, {
// //popup: "Bell 206 " + i,
// icon: helicopterIcon
//});
console.log(marker.hashCode);
markers.push(marker);
leafletView.RegisterMarker(marker);
console.log(markers);
console.log(markers.length)
obj.id = marker.hashCode;
//ws.send(marker.hashCode); // テキスト送信
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
} else if ((Math.abs(obj.lat) > 90.0) || (Math.abs(obj.lng) > 180.0)){ // 異常な座標が入った場合は、マーカーを消去する
console.log("Math.abs(obj.lat) > 180.0)")
for (let i = 0; i < markers.length; ++i) {
if (obj.id == markers[i].hashCode){
console.log(i)
console.log(obj.id)
console.log("obj.id == markers[i].hashCode")
//leafletView.RemoveMarkers(markers[obj.id]); // これでは消えてくれません
// 1つのマーカーを消すのに、面倒でも以下の2行が必要
var deleteList = markers.splice(i, 1);
leafletView.RemoveMarkers(deleteList);
// 以下失敗例リスト
//leafletView.RemoveMarkers(markers[i].hashCode); //これはダメ
//leafletView.RemoveMarkers(markers[obj.id],'item');
//leafletView.ProcessView(); // 試しに入れてみる
//leafletView.RemoveMarkers(markers[i-1]);
//leafletView.RemoveMarkers(markers);
break;
}
}
obj.lat = 91.0;
obj.lng = 181.0;
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
} else {
// 位置情報更新
console.log("else")
for (let i = 0; i < markers.length; ++i) {
if (obj.id == markers[i].hashCode){
var ll = markers[i].position;
ll.lat = obj.lat;
ll.lng = obj.lng;
break;
}
}
var json_obj = JSON.stringify(obj);
ws.send(json_obj);
}
}
// 位置情報の更新
window.setInterval(function () {
leafletView.ProcessView(); // 変更が行われたときに呼び出されれなければならない
}, 1000);
// サーバを止めると、ここに飛んでくる
ws.onclose = function(event) {
//print("CLOSE");
ws = null;
}
map.addLayer(leafletView);
</script>
</body>
</html>
`))
// pm_proxy5_single_socket.go
/*
このプログラムは、CSVファイルに記載された順番にエージェントの位置情報を読み取り、
WebSocket を介してサーバーへ送信・更新する。
サーバへ転送するプログラムは、server22-1.go
- WebSocket 切断時に自動で再接続(最大5回リトライ)
- エージェントが移動した場合のみサーバーへ通知
- `sync.Mutex` を使用してスレッド安全な処理を実現
- 位置情報が 999.0 以上の場合、エージェントを削除
csv情報のサンプル (IDと緯度経度のみ使っている
50044035447,139.629538,35.365357,00:00:00,WALK
50044035447,139.629430,35.365151,00:00:30,WALK
50044035447,139.629321,35.364945,00:01:00,WALK
50044035447,139.629213,35.364738,00:01:30,WALK
50044035447,139.629104,35.364532,00:02:00,WALK
50044035447,139.628996,35.364325,00:02:30,WALK
50044035447,139.628888,35.364119,00:03:00,WALK
50044035447,139.628787,35.363937,00:03:30,WALK
50044035447,139.628742,35.364159,00:04:00,WALK
50044035447,139.628699,35.364375,00:04:30,WALK
50044035447,139.628654,35.364592,00:05:00,WALK
50044035447,139.628533,35.364724,00:05:30,WALK
50044035447,139.628261,35.364691,00:06:00,WALK
50044035447,139.627989,35.364658,00:06:30,WALK
50044035447,139.627716,35.364625,00:07:00,WALK
50044035447,139.627680,35.364620,00:07:30,WALK
50044035447,999.9,999.9,00:08:00,WALK
(50044035447, 999.9, 999.9, 00:08:00, WALK) でエージェントの移動が終了し、サーバからアイコンが消滅する
*/
package main
import (
"encoding/csv"
"flag"
"fmt"
"log"
"math"
"net/url"
"os"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
)
const (
TopLeftLat = 35.37574882601201
TopLeftLon = 139.61403393574466
BottomRightLat = 35.36163108058289
BottomRightLon = 139.6297897196359
)
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type unmTbl struct {
uniName string
objType string
simNum int
pmNum int
lon float64
lat float64
}
var list = make([]unmTbl, 0)
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
var mutex sync.Mutex
func connectWebSocket() (*websocket.Conn, error) {
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
for i := 0; i < 5; i++ {
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err == nil {
log.Println("Reconnected to WebSocket successfully.")
return c, nil
}
log.Println("WebSocket reconnection attempt failed:", err)
time.Sleep(2 * time.Second)
}
return nil, fmt.Errorf("failed to reconnect WebSocket after multiple attempts")
}
func main() {
file, err := os.Open("agent_track_2018_101_end_and_order.csv")
if err != nil {
panic(err)
}
defer file.Close()
fmt.Println("Opened testtest.csv")
reader := csv.NewReader(file)
var line []string
flag.Parse()
log.SetFlags(0)
c, err := connectWebSocket()
if err != nil {
log.Fatal("WebSocket connection failed:", err)
}
defer c.Close()
for {
time.Sleep(time.Millisecond * 10)
line, err = reader.Read()
if err != nil {
break
}
uniName := line[0]
objType := line[4]
lon, _ := strconv.ParseFloat(line[1], 64)
lat, _ := strconv.ParseFloat(line[2], 64)
flag := 0
for i := range list {
if list[i].uniName == uniName {
old_lat := list[i].lat
old_lon := list[i].lon
list[i].lon = lon
list[i].lat = lat
if math.Abs(list[i].lat-old_lat) > 0.000000001 || math.Abs(list[i].lon-old_lon) > 0.000000001 {
gl := GetLoc{ID: list[i].pmNum, Lat: list[i].lat, Lng: list[i].lon}
mutex.Lock()
err = c.WriteJSON(gl)
if err != nil {
log.Println("write2 error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
gl3 := new(GetLoc)
err = c.ReadJSON(gl3)
if err != nil {
log.Println("read error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
mutex.Unlock()
println("move Object!")
if list[i].lat > 999.0 || list[i].lon > 999.0 {
list = append(list[:i], list[i+1:]...)
println("-----------------------------------------------------------------> delete Object!")
}
}
flag = 1
break
}
}
if flag == 0 {
uniNum := len(list)
ut := unmTbl{uniName: uniName, objType: objType, simNum: uniNum, lat: lat, lon: lon}
gl := new(GetLoc)
gl.ID = 0
gl.Lat = ut.lat
gl.Lng = ut.lon
mutex.Lock()
err := c.WriteJSON(gl)
if err != nil {
log.Println("write1 error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
gl2 := new(GetLoc)
err = c.ReadJSON(gl2)
if err != nil {
log.Println("read error:", err)
mutex.Unlock() // **ロック解除**
c.Close()
c, err = connectWebSocket()
if err != nil {
log.Println("WebSocket reconnection failed:", err)
return
}
continue
}
mutex.Unlock()
ut.pmNum = gl2.ID
list = append(list, ut)
println("------------>generate Object!")
}
}
}
vscodeでデバッグをする時に、同じディレクトリにmain()関数を持つGo言語プログラムが複数あると、デバッグができません。ファイル名を指定してデバッグすることはできないでしょうか。ただし、launch.json にファイル名を書き込むと、実行ファイルが固定されてしまうので、それは避けたいのですが。
launch.json
を使うと、特定のGoファイルが環境として固定されるため、別のファイルをデバッグしたいときに launch.json
を都度編集する必要があり、やや不便です。
ですが、以下の方法を使えば 環境を固定せずに、デバッグするファイルを柔軟に切り替えられます。
launch.json
の ${file}
を使う環境を固定せずに、現在開いているGoファイルをデバッグする方法です。
${file}
は、現在アクティブな(開いている)ファイルをデバッグ対象にする特殊変数です。開いているGoのプログラムでデバッガーが動いた。これから作業がラクになるかな。
さすがに3つの案件の同時並列処理は無理 ―― と、泣き言を言いながら、今、本当に泣きそうです。
I'm whining that “it's impossible to handle three projects simultaneously,” and I'm about to cry.
経験的に、こういう時『もっと大変な人が、沢山いる』という考え方をすることは、悪手です。
Empirically, thinking that “there are many people in worse situations” is a bad idea in a situation like this.
他人なんぞ、どーでもいい。
I don't care about other people.
「私が辛い」という状態だけで、必要かつ十分です。
The fact that I am in pain is sufficient.
-----
そういえば、今年の年賀状には、長女の結婚式の写真の下に、自分のコメントとして
Speaking of which, on this year's New Year's card, under the photo of my eldest daughter's wedding, I remember I wrote the following comment
『昨年、"うつ"になりました』
'I became depressed last year.'
と書いたのを思い出しました。
『これでは、長女の結婚で"うつ"になったように読める』と気がついたのは、年賀状をポストに投函した後でした。
It was only after I had posted the New Year's card in the post that I realized that it could be read as if I had become depressed because of my eldest daughter's marriage.
しかし、私を"うつ"にするのは、いつだって、仕事です。
However, it is always work that makes me depressed.
その正体の多くが、動かないプログラムであり、停止するシステムです。
Many are programs that don't work and systems that become stop.
世の中には「頼めない仕事」というものが、あるのです。
There are some jobs that I can't ask someone to do.
bytes-received send-rate-mbps receive-rate-mbps bandwidth-mbps rtt-ms cb 0 0 0 12 100 1706 0 1.128166 0 6.216 43.597 2000 0 3.958326 0 10.008 80.069 1831 0 4.864244 0 69.084 74.593 2000
この表にでてくる、send-rate-mbps receive-rate-mbps bandwidth-mbps の単位は何ですか
表にある send-rate-mbps
、receive-rate-mbps
、bandwidth-mbps
の単位は「Mbps(メガビット毎秒)」 です。
項目 | 意味 | 単位 |
---|---|---|
send-rate-mbps | 送信レート(今使っている帯域) | Mbps(メガビット毎秒) |
receive-rate-mbps | 受信レート(今使っている帯域) | Mbps(メガビット毎秒) |
bandwidth-mbps | 推定利用可能帯域(まだ使える帯域) | Mbps(メガビット毎秒) |
send-rate-mbps
(送信レート)send-rate-mbps = 4.864244
なら、約 4.86 Mbps でデータを送信している。receive-rate-mbps
(受信レート)receive-rate-mbps = 0
なので、受信データはない。bandwidth-mbps
(推定帯域幅)bandwidth-mbps = 69.084
なら、SRTは「この回線では約 69.08 Mbps まで使える」と推定している。1 Mbps(メガビット毎秒)は:
例えば:
send-rate-mbps = 4.86 Mbps
send-rate-mbps
、receive-rate-mbps
、bandwidth-mbps
の単位は「Mbps(メガビット毎秒)」send-rate-mbps
は今使っている送信帯域receive-rate-mbps
は今使っている受信帯域bandwidth-mbps
はSRTが推定した利用可能な最大帯域このデータから、送信レートは 最大 4.86 Mbps であり、ネットワークが 最大 69 Mbps まで利用可能 と推定されていることが分かります。
GStreamerのC言語のライブラリの、res = gst_structure_get_double(stats, "bandwidth-mbps", &bandwidth_mbps); g_print("bandwidth-mbps: %d %f\n", res, bandwidth_mbps);の値が何の値を返しているのか教えて下さい
このプログラムにおける bandwidth_mbps
の値は、SRT(Secure Reliable Transport)通信における推定帯域幅(Mbps) を示していると考えられます。
bandwidth_mbps
の取得と用途bandwidth_mbps
は、以下のコードで取得されています。
この部分では、GStreamer の srtserversink
から取得できる SRTの統計情報 (stats
) から "bandwidth-mbps"
というキーの値を取得しています。
GStreamer の SRTプラグイン (srtserversink
) は、SRTプロトコルのメトリクス情報を提供し、その中に "bandwidth-mbps"
というパラメータが含まれています。
つまり、SRT通信時の「利用可能な推定帯域幅」をリアルタイムで取得している。
bandwidth_mbps
の意味bandwidth_mbps
の用途「SRTの推定帯域幅(Mbps)」というのは、「今使っている帯域なのか」、「まだ使える帯域なのか」どちらでしょうか
bandwidth_mbps
は、SRT (Secure Reliable Transport) における「推定帯域幅 (Estimated Bandwidth)」を示す値であり、その意味合いは 「まだ使える帯域」= 利用可能な推定帯域 に近いものです。
bandwidth_mbps
は「今使っている帯域」ではないプログラム内では、SRTの統計情報 (stats
) から bandwidth-mbps
の他に、send-rate-mbps
や receive-rate-mbps
という値も取得できるようになっています。
send-rate-mbps
→ 現在の送信レート(今実際に使っている帯域)receive-rate-mbps
→ 現在の受信レート(今実際に使っている帯域)bandwidth-mbps
→ SRTプロトコルが推定した、利用可能な最大帯域このことから、bandwidth-mbps
は「現在使っている帯域」ではなく、「ネットワークが許容できる帯域の推定値」 であると考えられます。
bandwidth-mbps
の定義(SRT公式の仕様より)SRTの公式仕様では、"Estimated Bandwidth"(推定帯域)は次のように説明されています。
bandwidth-mbps: The estimated maximum available bandwidth of the connection, calculated by the SRT protocol based on observed network conditions such as packet loss and RTT.
つまり、SRTが「この回線ではこのくらいの帯域が利用できるはずだ」と推定している値 であり、今実際に使用している帯域とは違う ということです。
指標 | 意味 | 役割 |
---|---|---|
send-rate-mbps | 今使っている帯域(送信速度) | 実際の送信速度をモニタリング |
receive-rate-mbps | 今使っている帯域(受信速度) | 実際の受信速度をモニタリング |
bandwidth-mbps | まだ使える帯域(推定値) | SRTが計算した利用可能な最大帯域 |
✅ bandwidth_mbps
は「まだ使える帯域」= 利用可能な推定帯域
✅ SRTがネットワーク状況を解析して算出した、回線が許容できる最大帯域を示している
✅ 「今使っている帯域(現在の送信速度)」ではなく、「どのくらいの帯域が利用可能か」という指標
この値を使って、適切なビットレートを動的に調整し、映像配信を安定させる仕組みになっています。