2022/04,江端さんの技術メモ

「OpenStreetMapから鉄道路線データを落とせない」と愚痴っていたら、社内の人から「OSMに鉄道データ入っていますよ」と言われて、愕然としました。


江端:「私は、鉄道のOSMデータの取り方に辿り付けていません。いつも、ここからOSMデータをダウンロードしていますが、地理情報しか取れていない(ように思えます)。お暇な時に御教授頂けましたら幸いです。」

同僚:「そこから取れるデータの中に、鉄道路線のデータも入っています」

江端: ―― はい?

江端:「かなりずうずうしいお願いをしていることは分かっているのです、このタグをPostGISのpostgresqlに落す方法とかご存知だったりしますか? 多分、この辺(Configuration file = /usr/local/share/osm2pgrouting/mapconfig_for_cars.xml)です。」

同僚:「私は、JuliaのOpenStreetMapX.jlを少し改造して、直接OpenStreetMapのosmファイルをパースしていました」

https://github.com/pszufe/OpenStreetMapX.jl


で、現在、readme.mdを読んでいます。

  • Open Street Mapデータの空間解析・シミュレーション・可視化用パッケージ(プロット機能は別パッケージで提供されます)
  • このパッケージの目的は、都市のマルチエージェントモデリングとシミュレーションのためのバックボーンを提供することです。
  • このパッケージは *.osm と *.pbf (@blegat によって寄贈) ファイルを解析し、メタデータに沿った Graphs.jl 表現を生成することができます。

うむ、この連休の使い方としては、有意義な時間を過せそうです。

ところで、Juliaって、コンピュータ言語かな。これ以上、言語は、覚えたくないんだけど ―― 仕方ないですね。

2022/04,江端さんの技術メモ

以前、こちらで、redisのブロードキャスト(Pub/Sub)の方法について2つほど紹介しましたが、試した結果、こっちの方が安定して調子が良くて、現在、こちら(redigo)を使っています

redisを前提として、golangでPubSubを実現するプログラム

Redigoを使う(6) パブリッシュ/サブスクライブ

で紹介されていた、サンプルプログラムを使って、構造体のデータを丸ごと送信しようとしたのですが、Golangの厳しい型チェックに掴まって、なかなか上手く動かすことができません。

ただ、構造体をJSON形式にすれば、成功することは分かっています。

golang内でredis経由でJSONを飛す時、golangから直接JavaScriptへJSONを飛す時の覚え書き

しかし、今の段階で構造体をJSONに変更すると、その影響が、プログラム全体に波及し、作業が膨大になるので、これはしたくありませんでした。

もしかしたら、構造体のブロードキャストも、JSONの時と同じように、"json.Unmarshal"、 "json.Marshal" を使えばいけるかな? と思ってやってみたら、あっさりと成功しました。

上記の記事のサンプルプログラムを、構造体データ送付用に改造したものを開示しておきます。

■パブリッシャ(発行元)側

// go run pub.go
// goga\1-9-6\others\pub.go
package main

import (
	"encoding/json"
	"fmt"

	"github.com/gomodule/redigo/redis"
)

type Ch5_info struct {
	Bus_num int     // バスの番号
	CC      int     //  1: 座標情報 2: 停車位置情報
	Present int     // 停車位置番号
	Lat     float64 //
	Lon     float64 //
}

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c5i := new(Ch5_info)

	c5i.Bus_num = 1
	c5i.CC = 2
	c5i.Present = 23
	c5i.Lat = 12.34
	c5i.Lon = 56.78

	json_c5i, _ := json.Marshal(c5i)

	// パブリッシュ
	r, err := redis.Int(conn.Do("PUBLISH", "channel_1", json_c5i))
	if err != nil {
		panic(err)
	}
	fmt.Println(r)
}

■サブスクライブ(購読者)側

// go run sub.go
// goga\1-9-6\others\sub.go

package main

import (
	"encoding/json"
	"fmt"

	"github.com/gomodule/redigo/redis"
)

type Ch5_info struct {
	Bus_num int     // バスの番号
	CC      int     //  1: 座標情報 2: 停車位置情報
	Present int     // 停車位置番号
	Lat     float64 //
	Lon     float64 //
}

func main() {
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe("channel_1", "channel_2", "channel_3")
	for {
		switch v := psc.Receive().(type) {
		case redis.Message:
			fmt.Printf("%s: message: %s\n", v.Channel, v.Data)

			c5i := new(Ch5_info)

			_ = json.Unmarshal(v.Data, &c5i)

			// 試しに2つほど出力してみる
			fmt.Println(c5i.Bus_num)
			fmt.Println(c5i.Lon)

		case redis.Subscription:
			fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
		case error:
			return
		}
	}
}

私のような悩み持っている人、世界中に沢山いました。
Golangは、C/C++みたいに、自己責任で自由にキャストできないんですよね。
memset()を使って、大量の配列の変数を一気に変更させてしまう、ということもできないようで、Golang不便だなぁ、って思います。

2022/04,江端さんの技術メモ

Golang で複数のgoroutineで、1つのchannelを使い回す場合(要求応答通信)をする際の注意点について

■Case 1

package main

import (
	"fmt"
	"sync"
)

//var Ch1 chan interface{}
var Ch1 chan interface{}

var mutex sync.Mutex
var wg sync.WaitGroup

func funcA(num int) {
	fmt.Println("Staring funcA No.", num)
	for i := 0; i < 10; i++ {
		mutex.Lock()
		fmt.Println("funcA:", num, " send", i+num*10)
		Ch1 <- i + num*10
		k := <-Ch1
		fmt.Println("				returned:", k)
		mutex.Unlock()
	}
	wg.Done()
}

func echo(num int) {
	fmt.Println("Staring echo No.", num)

	for {
		i := <-Ch1
		Ch1 <- i
	}

}

func main() {

	Ch1 = make(chan interface{}) // チャネルの初期化

	wg.Add(1)
	go funcA(1)

	wg.Add(1)
	go funcA(2)

	wg.Add(1)
	go echo(1)

	wg.Wait()
}

結果

funcA: 1  send 15
                                returned: 15
funcA: 1  send 16
                                returned: 16
funcA: 1  send 17
                                returned: 17
funcA: 1  send 18
                                returned: 18
funcA: 1  send 19
                                returned: 19
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x0)
        c:/go/src/runtime/sema.go:56 +0x25
sync.(*WaitGroup).Wait(0x483fc0)
        c:/go/src/sync/waitgroup.go:130 +0x71
main.main()
        C:/Users/ebata/goga/0-14/main.go:52 +0x131

goroutine 8 [chan receive]:
main.echo(0x0)
        C:/Users/ebata/goga/0-14/main.go:32 +0x8a
created by main.main
        C:/Users/ebata/goga/0-14/main.go:50 +0x125
exit status 2

■Case 2

# 最後に、close(Ch1)を入れてみた

        wg.Add(1)
	go echo(1)

	wg.Wait()
	close(Ch1)
}

結果

funcA: 2 send 25
                                returned: 25
funcA: 2  send 26
                                returned: 26
funcA: 2  send 27
                                returned: 27
funcA: 2  send 28
                                returned: 28
funcA: 2  send 29
                                returned: 29
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [semacquire]:
sync.runtime_Semacquire(0x0)
        c:/go/src/runtime/sema.go:56 +0x25
sync.(*WaitGroup).Wait(0x1e3fc0)
        c:/go/src/sync/waitgroup.go:130 +0x71
main.main()
        C:/Users/ebata/goga/0-14/main.go:52 +0x131

goroutine 8 [chan receive]:
main.echo(0x0)
        C:/Users/ebata/goga/0-14/main.go:32 +0x8a
created by main.main
        C:/Users/ebata/goga/0-14/main.go:50 +0x125
exit status 2

■Case 3

//wg.Add(1)
	go echo(1)

	wg.Wait()
	close(Ch1)
}

結果

funcA: 2  send 29
                                returned: 29
funcA: 1  send 17
                                returned: 17
funcA: 1  send 18
                                returned: 18
funcA: 1  send 19
                                returned: 19

C:\Users\ebata\goga\0-14>

結論

(1)channelを応答通信で使いたいのであれば、送信側(funcA)の中身をMutexでロックする必要がある。受信側は、一つだけならロックの必要はない
(2)送信側のgoroutineが消えると、受信側(echo)が『相手がいなくて寂しくなって』エラーを吐くので、chose(Ch1)で、きっちりchannelを殺しておくこと
(3)受信側(echo)が無限待ち"for()"であるなら、wg.Add(1)を発行してはならない。なぜならechoは、原則として終了しないgoroutineだから。

―― しかし、そこまで構わなくてもいいんじゃなかな、と思う。Golangは、相当"過保護"に作られていると思う。

以上

 

 

2022/04,江端さんの技術メモ

1つのJavaScriptに2つのWebSocket繋げようとしたのですが、upgrade:websocket: request origin not allowed by Upgrader.CheckOrigin というエラーメッセージがでてきて、コネクションに失敗します。

で、よく分からんのですが、1行追加したら、直りました。

upgrader.Upgrade(w, r, nil)

の前に、

upgrader.CheckOrigin = func(r *http.Request) bool { return true }
を1行追加。
以下がサンプルコードです。
func echo2(w http.ResponseWriter, r *http.Request) {

	upgrader.CheckOrigin = func(r *http.Request) bool { return true }

	c, err := upgrader.Upgrade(w, r, nil) // cはサーバのコネクション
	if err != nil {
		log.Print("upgrade:", err)
		return
	}
	defer c.Close()

まあ、相変わらず、理由は分からんのですが、結果オーライです。

2022/04,江端さんの技術メモ

出発地と到着地のマーカを、予め2つ作っておいて、それを指かマウスで動かして、出発地と到着地の座標を得るJavaScript(の一部)

var map = L.map("map", {
            attributionControl: false,
            zoomControl: false
        }).setView(new L.LatLng(35.654543, 139.795534), 18);
        // }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴

        L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
            detectRetina: true,
            maxNativeZoom: 18
        }).addTo(map);

        var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト


        // マーカーを2つ作ってみる
        // マーカーを1つめ()
        o_icon = L.icon({iconUrl:'http://kobore.net/soft/O_50.png',
        //iconSize: [36, 60], iconAnchor: [18, 60], popupAnchor:[0,-60]});
        iconSize: [36, 60], iconAnchor: [18, 60]});

        var o_marker = L.marker(
            [35.654543, 139.795534],
            { popup: 'Origin', draggable: true,  opacity:1.0, icon:o_icon}
        ).addTo(map);

        o_marker.on('dragstart', () => { // マーカーがドラッグしている最中
            o_marker.setOpacity(0.6); // 透明度0.6に
        });

        o_marker.bindPopup(`${o_marker.getLatLng().lat}, ${o_marker.getLatLng().lng}`)

        o_marker.on('dragend', () => { // マーカーが停止した時
            o_marker.setOpacity(1); // 透明度1.0へ
            o_marker.bindPopup(`${o_marker.getLatLng().lat}, ${o_marker.getLatLng().lng}`);
            console.log(o_marker);       
        });        

        d_icon = L.icon({iconUrl:'http://kobore.net/soft/D_50.png',
        //iconSize: [36, 60], iconAnchor: [18, 60], popupAnchor:[0,-60]});
        iconSize: [36, 60], iconAnchor: [18, 60]});

        var d_marker = L.marker(
            [35.654543, 139.797],
            { popup: 'Destination', draggable: true,  opacity:1.0, icon:d_icon}
        ).addTo(map);

        d_marker.bindPopup(`${d_marker.getLatLng().lat}, ${d_marker.getLatLng().lng}`)

        d_marker.on('dragstart', () => { // マーカーがドラッグしている最中
            d_marker.setOpacity(0.6); // 透明度0.6に
        });

        d_marker.on('dragend', () => { // マーカーが停止した時
            d_marker.setOpacity(1);  // 透明度1.0へ
            d_marker.bindPopup(`${d_marker.getLatLng().lat}, ${d_marker.getLatLng().lng}`);
            console.log(d_marker);       
        });

この手のコーディングのサンプル、本当に少ないです。

ちなみに、今更ながらなのですが、PrumeMobile の位置付けはこんな感じです。

 

2022/04,江端さんの技術メモ

異動先の部署でのキックオフの事務局長になったのは良いのですが、異動先の部署では、Teamsを使い倒しているので、四苦八苦しています。

何とか前任者と同じ「かっこいい」フォームを使って、チームへの投稿をしたいのですが、やりかたが分からくて、同僚に泣きついています。

で、先程教えて貰ったページを忘れない内に記載しておきます。

Teamsでアナウンスを投稿する方法

 

2022/04,江端さんの技術メモ

https://teratail.com/questions/2m8dsx1urnslib

一応動いているソースコード

// main.go

package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"net/url"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{}

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"`
}

func echo2(w http.ResponseWriter, r *http.Request) {
	conn2, err := upgrader.Upgrade(w, r, nil) //conn2でwebsocketを作成
	if err != nil {
		log.Println("websocket connection err:", err)
		return
	}
	defer conn2.Close()

	for {
		gl2 := new(GetLoc)
		err := conn2.ReadJSON(&gl2)
		if err != nil {
			log.Println("ReadJSON:", err)
			break
		}
		fmt.Println("echo2:", gl2)
		//time.Sleep(time.Second * 10)
	}

}

var addr = flag.String("addr", "0.0.0.0:5000", "http service address") // テスト

func dummyClient() {
	_ = 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()

	for {
		gl := new(GetLoc)

		gl.ID = 101
		gl.Lat = 181.0
		gl.Lng = 181.0
		gl.TYPE = "BUS"
		gl.POPUP = 101

		err := c.WriteJSON(gl)
		if err != nil {
			log.Println("write:", err)
			break
		}
	}

}

func main() {
	http.Handle("/", http.FileServer(http.Dir(".")))

	http.HandleFunc("/echo2", echo2)
	//go dummyClient()

	log.Println("server starting...", "http://localhost:5000")
	log.Fatal(http.ListenAndServe("localhost:5000", nil))
}
// index.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>

</head>
<body>
    <form>
        <button id="open">open</button>
        <button id="send">send</button>
        <button id="close">close</button>
    </form>

    <script>  

        var ws;

        // websocketのオープン(この段階で接続完了)
        ws = new WebSocket('ws://localhost:5000/echo2')  // ユーザ登録画面

        // websocketのオープンイベントが発生したら
        ws.onopen = function(evt) {
            // 何もしない
            print("OPEN");
            console.log("OPEN");
            return false;
        }

        // "send"ボタンをクリックしたら
        document.getElementById("send").onclick = function(evt) {
            if (!ws) {
                return false;
            }
 
            var obj = {id:101,lat:181.0, lng:181.0,type:"BUS", popup:101}
            var json_obj = JSON.stringify(obj);            
            ws.send(json_obj);
            return false;
         };
        
        // "close"ボタンをクリックしたら
        document.getElementById("close").onclick = function(evt) {
            if (!ws) {
                return false;
            }
            // websocketを閉じる
                
            ws.close();
            console.log("CLOSE")
            return false;
        };
   
    </script>
</body>
</html>

2022/04,江端さんの技術メモ

leafletの地図上に、マウスでクリックするとマーカーが出てきて、マーカーをクリックするとマーカーが消える、というJavaScript

var map = L.map("map", {
        attributionControl: false,
        zoomControl: false
        }).setView(new L.LatLng(35.654543, 139.795534), 18);
        // }).setView(new L.LatLng(35.598563, 139.475528), 18); 広袴

        L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
         detectRetina: true,
         maxNativeZoom: 18
        }).addTo(map);

        var leafletView = new PruneClusterForLeaflet(1,1);  // (120,20)がデフォルト

        //地図のclickイベントでonMapClick関数を呼び出し
        map.on('click', onMapClick)
        
        function onMapClick(e) {
            //地図のclickイベント呼び出される
            //クリック地点の座標にマーカーを追加、マーカーのclickイベントでonMarkerClick関数を呼び出し
            var mk = L.marker(e.latlng).on('click', onMarkerClick).addTo(map);
            console.log("click:",e.latlng)
        }

        function onMarkerClick(e) {
            //マーカーのclickイベント呼び出される
            //クリックされたマーカーを地図のレイヤから削除する
            map.removeLayer(e.target);
            console.log("remove:",e.target)
        }

2022/04,江端さんの技術メモ

PrumeMobileのサーバ化

から、1日もあれば、動かせるだろうと思ったのですが、そこから、redisのパブサブ、Golangのmap、JavaScriptの改造、えらい目に会いましたが、ようやく目処が立ちました。

PrumeMobileは、Webのメモリを使うので、単なるWebサーバのようには取り扱えないので、色々工夫をしました。デバッグで苦労しました。

嬉しい気持ちは、直ぐに消えてしまうので、消える前に記録しておきました。