から、1日もあれば、動かせるだろうと思ったのですが、そこから、redisのパブサブ、Golangのmap、JavaScriptの改造、えらい目に会いましたが、ようやく目処が立ちました。
PrumeMobileは、Webのメモリを使うので、単なるWebサーバのようには取り扱えないので、色々工夫をしました。デバッグで苦労しました。
嬉しい気持ちは、直ぐに消えてしまうので、消える前に記録しておきました。
江端智一のホームページ
から、1日もあれば、動かせるだろうと思ったのですが、そこから、redisのパブサブ、Golangのmap、JavaScriptの改造、えらい目に会いましたが、ようやく目処が立ちました。
PrumeMobileは、Webのメモリを使うので、単なるWebサーバのようには取り扱えないので、色々工夫をしました。デバッグで苦労しました。
嬉しい気持ちは、直ぐに消えてしまうので、消える前に記録しておきました。
// パブリッシュ側
type GetLoc struct {
ID int `json:"id"`
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
TYPE string `json:"type"` // "PERSON","BUS","CONTROL
POPUP int `json:"popup"`
//Address string `json:"address"`
}
gl.TYPE = "BUS"
gl.Lat = 35.654543 + (rand.Float64()-0.5)*0.00001*20
gl.Lng = 139.795534 + (rand.Float64()-0.5)*0.00002*20
gl.ID = rand.Int() % 5
json_gl, _ := json.Marshal(gl)
r, err := redis.Int(conn.Do("PUBLISH", "channel_1", json_gl))
// サブスクライブ側
for {
switch v := psc.Receive().(type) {
case redis.Message:
fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
var gl GetLoc
_ = json.Unmarshal(v.Data, &gl)
fmt.Println(gl.ID)
var gl GetLoc
_ = json.Unmarshal(v.Data, &gl)
fmt.Println(gl.ID)
//conn.WriteJSON(v.Data)
conn.WriteJSON(gl)
JavaScriptで受けとる
<script>
function obj(id, lat, lng, type, popup){
this.id = id;
this.lat = lat;
this.lng = lng;
this.type = type;
this.popup = popup;
}
//ws.onmessage = e => console.log(e.data)
ws.onmessage = function(event) { // 受信したメッセージはここに飛んでくる
console.log("RESPONSE",event.data)
var obj = JSON.parse(event.data);
console.log("after parse:",obj.id)
}
ポイントは、Marshal、Unmarshal、
parseの使い方
入力2、出力1のマップを取扱いたい場合のケースを調べてみました。
Golangは、2次元マップの取扱いは簡単なのですが、N次元は、かなり複雑になるようです。調べたところ、以下のような取扱いができることが分かりました。
package main
import "fmt"
func main() {
// 2次元マップ
m := map[string]int{}
//m := make(map[string]int)
m["apple"] = 150
m["banana"] = 200
m["lemon"] = 300
fmt.Println("0:", m["apple"])
// 多次元mapは、2次元マップのように簡単には使えない
// 比較的面倒のないやり方は、以下のように構造体を使う方法(らしい)
// https://qiita.com/ruiu/items/476f65e7cec07fd3d4d7
type key struct {
id int
att string
}
// 配列宣言
m1 := make(map[key]int)
// レコード追加
m1[key{0, "BUS"}] = 1
m1[key{0, "PERSON"}] = 1
m1[key{1, "BUS"}] = 2
m1[key{1, "PERSON"}] = 11
// レコードの一つを表示
fmt.Println("1:", m1[key{1, "BUS"}])
// レコードの全部を表示
fmt.Println("2:", m1)
// 変数を使う方法
id0 := 1
att0 := "PERSON"
fmt.Println("3:", m1[key{id0, att0}])
// レコードの削除
delete(m1, key{1, "BUS"})
fmt.Println("4:", m1)
for i := range m1 {
fmt.Println("5:", i)
}
// 変数を使って、キーの存在を確認する
id0 = 1
att0 = "PERSON"
value, isThere := m1[key{id0, att0}]
fmt.Println("6:", value, isThere)
id0 = 2
att0 = "PERSON"
value, isThere = m1[key{id0, att0}]
fmt.Println("7:", value, isThere)
// レコード追加の例
id0 = 3
att0 = "BUS"
new_id0 := 20
fmt.Println("8:", m1)
_, isThere = m1[key{id0, att0}]
if !isThere {
m1[key{id0, att0}] = new_id0
}
fmt.Println("9:", m1)
}
出力結果は以下の通りです。
C:\Users\ebata\goga\1-11\test>go run main.go
0: 150
1: 2
2: map[{0 BUS}:1 {0 PERSON}:1 {1 BUS}:2 {1 PERSON}:11]
3: 11
4: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11]
5: {1 PERSON}
5: {0 BUS}
5: {0 PERSON}
6: 11 true
7: 0 false
8: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11]
9: map[{0 BUS}:1 {0 PERSON}:1 {1 PERSON}:11 {3 BUS}:20]
以上
で、型番を調べたら、
golangのプログラムの中でブロードキャストするのであれば、
https://github.com/MicrosoftArchive/redis/releases/tag/win-3.2.100
で、redisサーバをインスールして、
https://github.com/gomodule/redigoを使うという手もありますが、
のサンプルプログラムで簡単に試すことができます。
(1)redisサーバを援用して、(2)golangをサーバにして、(3)websocketでブロードキャストが実現できて、(4)JavaScriptをクライアントとして使える、(5)OSSを捜し出しました。
理由は、複数のWebに同じ地図と車両の移動をリアルタイムを表示しなればならないのですが、Websocketはユニキャスト通信しかできないので、困っていました(UDPとか使えればいいんですが、スマホでUDPが通るとは思えないので)。
WebSocketでブロードキャストを作るのが辛いので、これを援用したいと思います。
これは、http://localhost:5000 でブラウザを立ち上げると、サーバを介してブラウザ→ブラウザにメッセージがechoされます。
redisサーバを立ち上げておいて、main.goと、js/index.htmlだけで動きます。
上記を改造すれば、サーバからパブ(pub)、webブラウザでサブ(sub)が可能になるはずです ―― 多分
実験した結果、subscribeしたオブジェクトに、過去のデータもpublishされることが分かりました。
subscribe以前のデータを取込むと逆に困る(過去のデータは不要な上に、データの表示もバラバラになる)ので、pubsub-goの採用は見送り、redigoの方を使うことにしました。
JavaScriptを直接のクライアントとして使いたかったのですが、redigoと連携するJavaScriptが見つけられなかった(かなり捜したつもり)ので、Golangで立ち上げるハンドルの中で、個別に対処することにしました。
C:\Users\ebata\goga\1-10>のI_hate_go_server.md が本体です。
このドキュメントは、絶対的な意味において「無保証」です
Golangで作るサーバは、HandleやらHandlerやら、ハンドル、ハンドルとうるさい! と叫びたくなること、甚しいです。
さすがに、C言語のfork()まで戻りたいとは思えませんが、『あれは、あれで、何をやっているのか分かった』とは言えました。
で、もう正しい理解かどうかは、無視して、もう、誰の話も聞かん! 江端はこういう風に理解すると決めた!! ことを記載しておきます。
http.Handle("/", http.FileServer(http.Dir(".")))
は、https://xxx.xxx/ でアクセスできて(http://xxx.xxx/yyyy のように"yyyy"はない)、index.htmlが、goのサーバのプログラムと同じディレクトリ(".")にいる、と宣言するもの。
http.Handle("/tomo", http.FileServer(http.Dir("./js")))
であれば、https://xxx.xxx/tomo でアクセスできてindex.htmlが、goのサーバのプログラムと同じディレクトリのしたのjs("./js")にいる、と宣言するもの。
具体的には、こちらを読んで頂くと良いと思います。
要するにwebブラウザ(クライアント)からのアクセスがあれば、この関数がfork()の用に立ち上って、Webブラウザとの面倒を見る。→大嘘でした
面倒見ません。
まず第一に、http.HandleFunc()の誤解がありました。私は、これを、fork()のようにプロセスかスレッドを発生さるものと思っていましたが、これは、一言で言えば、単なるコールバック関数でした。
乱暴に言えば、Webからアクセスがあると、func()というファンクションに吹っ飛ばされる、という現象を発生させる"だけ"で、それ意外のことは何にもしてくれないのです。
これは、index.htmlの内容をクライアントの押しつけるfork()関数と考えれば足る。→大嘘でした
(後で述べるが)これで、
http://localhost:8080
でアクセスできるようになる
http.ServerFileというのは、実装されているので、わざわざ main.goに書く必要はない。
一方、
http.HandleFunc("/chat", HandleClients)
は、
func HandleClients(w http.ResponseWriter, r *http.Request) {
//色々
}
で定義されている、コードをfork()のように立ち上げるものである、と考えれば足る。→大嘘でした(現在修正検討中)
単にその関数に飛んでいくだけです(但し、ソケット情報を付けてくれます)
"/chat"とは何か?
(後で述べるが)これで、
http://localhost:8080/chat
でアクセスできるようになる
上記の関数は、"/"やら、"/chat"やらの(相対的)なパスを指定しているが、これは、サーバのアクセスするアドレスとポートを決定するものである。
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("error starting http server::", err)
return
}
で、これを宣言することで、サーバとして使えるようになる。
ちなみに、(":8080", nil)の"nil"は、上記のhttp.ServerFile()と、http.HandleFunc()を使うぜ、の意味になる(直接編集することもできるらしい)。
これは"github.com/gorilla/websocket"が提供してくれるもので、HTTP通信(一方通行)からWebSocket通信(相互通行)に更新してくれる便利なものらしい。
websocket, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal("error upgrading GET request to a websocket::", err)
}
こうしてしまえば、websocket.ReadJSON()やら、websocket.WriteHSON()やらが、バカスカ使えるようになる。
http.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir("static"))))
まあ、"/static/" は、通信コネクションでいいして、http.StripPrefix("/static", http.FileServer(http.Dir("static")))については、「/static」をhttp.FileServer()が捜索するURLから除く という意味です。
http.HandleFunc()と、http.ListenAndServe()の』2つだけ覚えておけば、いいんじゃない?、と思う。
一般的にクライアントはWebブラウザなんだけど、これをgolangのプログラムからwebsocketでアクセスしようとする場合は、こんな感じになる。
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
func bus(bus_num int) {
var bus BUS
///////////// 描画処理ここから ////////////////
_ = websocket.Upgrader{} // use default options
flag.Parse()
log.SetFlags(0)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
まず、グローバルで、以下のようにサーバを場所を書いておく。
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
(よく分からないんだけど)以下のような書き方でwebsocket(のインスタンス?)が作れるらしい。
_ = websocket.Upgrader{} // use default options
以下で、/echo2を使うぜ、の宣言
u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo2"}
で、以下で、websocket用のソケットができるらしい。
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
この後はc.ReadJSON()やら、c.WriteJSON()やらを使い倒す、ことができるようになります。
var addr = flag.String("addr", "0.0.0.0:8080", "http service address")
を"固定するもの"でいいのかな? → 間違っています。→ golang でコマンドライン引数を使う
import "log"
で、logを使う場合に、logの設定をリセットするもの、で良さそうです。
以上
内容間違っていたら、優しくご指摘下さい。
PrumeClusterは、Leafletをベースとして動く、スケーラブルオブジェクトビューアです。1万オブジェクトくらいなら軽く表示できます。
しかし、PrumeClusterは、クライアントのブラウザの中にアイコンのオブジェクトを直接作るので、基本的にはサーバとして使うことができません。クライアントとして使うものであて、描画画面は、常に"1つ"です。
で、私が作ったPrumeMobileもベースは、PrumeClusterなので、サーバとして使うことはできないのですが ―― 今週末、Vue.jpとかでスマホクライアント作ろうかと思ったのですが ――『もう新しいこと覚えるのは嫌だ』と思い知り、PrumeMobileのサーバ化を試みています ――ひとえに、考えうる限り、手を抜きたい、という一心からです。
複数のJavaScriptに対して、PrumeClusterがメッセージをブロードキャスト送付してくれるのか、くれないのか、明日調べよう。
最近"https"縛りがきつくて、ローカルのindex.htmlを叩くだけでは、画面が出てこなくなりました。正直、面倒くさいなぁ、と思っています。
こちらは、表示画面でブラウザを使いたいだけなのに、ブラウザ(特にchrome)が煩いことこの上もない。
これも時代の流れか、と諦めて、index.htmlを書いているディレクトリの内容で、サクッとサーバを立てる方法を、色々やってみましたので、メモを残しておきます。
まず、node.jsをインストールしてnpmを使えるようにしておきます。
面倒なので、私の環境に合わせて説明しますね(このディレクトリを隠す人、多いですけど、はっきり言って読み難い上に、あまり意味ない(外部から、ディレクトリに入れるところまでハックされたら、何をしても無駄))
という訳で、私の作業ディレクトリは、ここ→ ~/kese/leaflet です。
$ npm install -g http-server
$ http-server
と、これだけで、
ebata@DESKTOP-P6KREM0 MINGW64 ~/kese/leaflet$ http-serverStarting up http-server, serving ./http-server version: 14.1.0http-server settings:CORS: disabledCache: 3600 secondsConnection Timeout: 120 secondsDirectory Listings: visibleAutoIndex: visibleServe GZIP Files: falseServe Brotli Files: falseDefault File Extension: noneAvailable on:http://192.168.0.8:8080http://127.0.0.1:8080http://172.28.64.1:8080http://172.21.112.1:8080Hit CTRL-C to stop the server
でもって、ここから、Windows10で使えそうなバイナリをダウンロードしました。
ダウンロードしたところから、直接叩いてみたら、C:\Users\Ebata\AppData\Local\mkcert の中に、鍵ができていましたが、最初に、mkcer -installしろ、と言われています。
で、"localhost+1-key.pem"を "key.pem"とリネームして、"localhost+1.pem"を"cert.pem"とリネームして、~/kese/leafletに放り込みます。
そんでもって、~/kese/leaflet から
$ http-server -S -C cert.pem -o -p 8081
とすると、https 対応のサーバが立ち上がります。
ebata@DESKTOP-P6KREM0 MINGW64 ~/kese/leaflet$ http-server -S -C cert.pem -o -p 8081Starting up http-server, serving ./ through httpshttp-server version: 14.1.0http-server settings:CORS: disabledCache: 3600 secondsConnection Timeout: 120 secondsDirectory Listings: visibleAutoIndex: visibleServe GZIP Files: falseServe Brotli Files: falseDefault File Extension: noneAvailable on:https://192.168.0.8:8081https://127.0.0.1:8081https://172.28.64.1:8081https://172.21.112.1:8081Hit CTRL-C to stop the serverOpen: https://127.0.0.1:8081
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Open Street Map Test</title>
<style type="text/css">
html,body{ margin: 0px; }
</style>
<!--
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
-->
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
<script type="text/javascript" src="https://openlayers.org/api/2.13.1/OpenLayers.js"></script>
<script type="text/javascript">
// グーローバル変数の定義
var od;
var des_lonlat;
var arr_lonlat;
</script>
<script>
function MapInit(){
map = new OpenLayers.Map("MapCanvas");
var mapnik = new OpenLayers.Layer.OSM();
map.addLayer(mapnik);
//var lonLat = new OpenLayers.LonLat(139.47552, 35.59857)
var lonLat = new OpenLayers.LonLat(139.796182, 35.654285)
.transform(
new OpenLayers.Projection("EPSG:4326"),
new OpenLayers.Projection("EPSG:900913")
);
map.setCenter(lonLat, 17);
OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
initialize: function(options) {
this.handler = new OpenLayers.Handler.Click(
this, {
'click': this.onClick
}, this.handlerOptions
);
},
onClick: function(e) {
var lonlat = map.getLonLatFromPixel(e.xy);
lonlat.transform(
new OpenLayers.Projection("EPSG:900913"),
new OpenLayers.Projection("EPSG:4326")
);
var markers = new OpenLayers.Layer.Markers("Markers");
map.addLayer(markers);
var marker = new OpenLayers.Marker(
new OpenLayers.LonLat(lonlat.lon, lonlat.lat)
.transform(
new OpenLayers.Projection("EPSG:4326"),
new OpenLayers.Projection("EPSG:900913")
)
);
markers.addMarker(marker);
$("#LonLat").html("lon:" +lonlat.lon+ " lat:" +lonlat.lat);
if (od == "arrival"){
arr_lonlat = lonlat;
alert("arr_lonlatが設定されました" + arr_lonlat.lon +" " + arr_lonlat.lat);
} else if (od == "destination"){
des_lonlat = lonlat;
alert("des_lonlatが設定されました" + des_lonlat.lon +" " + des_lonlat.lat);
}
}
});
var click = new OpenLayers.Control.Click();
map.addControl(click);
click.activate();
}
</script>
<script type="text/javascript">
$(document).ready(function () {
$("#button01").on('click', function () {
od = "destination";
alert(od + " ボタン1がクリックされました。");
});
$("#button02").on('click', function () {
od = "arrival";
alert(od + " ボタン2がクリックされました。");
});
$("#button03").on('click', function () {
od = "confirmed"
alert(od + " ボタン3がクリックされました。");
//
});
})
</script>
</head>
<body>
<div id="MapCanvas" style="width:700px;height:700px;"></div>
<div id="LonLat"></div>
<input id="button01" type="button" value="Button1"/>
<input id="button02" type="button" value="Button2" />
<input id="button03" type="button" value="Button3" />
<script type="text/javascript">MapInit();</script>
</body>
</html>
最近の文章は、ほとんど、Visual Studio Code(vscode) でMarkdownを使って書いています。超ラクです。
で、「図面のコピペをMarkdownの文書の中にサクっと入れる」ことができないかな、と、ちょっとググってみたのですが、3秒で見つかりました。
https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image
メモとして、以下に記載しておきます。
■クリップボードから直接マークダウン/asciidoc(または他のファイル)に画像を貼り付けます。
準備: VSCODEの「拡張機能(Ctl+Shift+X)」から、"Paste Image"で検索→インストールを実施
使い方:
(1)画面をクリップボードに取り込む
(2)コマンドパレットを開く。Ctrl+Shift+P (MacではCmd+Shift+P)と入力します。
(3)"Paste Image"と入力するか、Ctrl+Alt+V (MacではCmd+Alt+V)と入力します。
(4)画像は、現在の編集ファイルを含むフォルダに保存されます。相対パスは、現在の編集ファイルに貼り付けられます。
以上