2023,江端さんの技術メモ

Go言語で、redisを使って2つの型の異なるデータをブロードキャストしている場合、その受信している部分を1つのswitchで受けとるにはどうしたら良いですか

を、異なるエージェントで、異なるメッセージを受信できるか試してみた件。

 

// C:\Users\ebata\tomioka3B\src\others\main28.go
// 2つのクロック(goroutine)を用意して、異なるエージェントで受けとれるかどうかの実験

package main

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

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


type Clock_Info struct {
	VirtualTime time.Time
	RealTime    time.Time
}

type SubClockInfo_2 struct {
    // 異なるデータ型のフィールドをここに追加
    SomeField string
    AnotherField int
}

func BaseClock() {

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

	// スタート時刻を指定
	startTime := time.Date(2023, 10, 1, 7, 0, 0, 0, time.UTC)

	// 1秒値を保持する変数
	seconds := 0

	var ci Clock_Info

	// ループを開始
	for {
		// 現在の時刻を計算
		ci.VirtualTime = startTime.Add(time.Duration(seconds) * time.Second)
		ci.RealTime = time.Now()

		// 現在の時刻を表示
		// fmt.Println("シミュレータの時刻:", ci.VirtualTime.Format("2006/01/02 15:04:05"))
		// fmt.Println("現在の時刻:", ci.RealTime.Format("2006/01/02 15:04:05")) // "2006/01/02 15:04:05"はフォーマットの形を真似るもので、内容に意味なし

		// パブリッシュ
		json_ci, _ := json.Marshal(ci)
		r, err := redis.Int(conn.Do("PUBLISH", "ClockInfo_1", json_ci))
		if err != nil {
			panic(err)
		}
		fmt.Println(r)

		// 5秒待つ (実際は、0.05秒くらいだが、確認用に長くしている)
		time.Sleep(5000 * time.Millisecond)

		// 1秒値を増加させる
		seconds++
	}
}


func SubClock() {  // 実験用に追加(時間ではなく、単なる文字列と数値を送り込むだけ)

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


	// 1秒値を保持する変数
	seconds := 0

	var sci2 SubClockInfo_2

	// ループを開始
	for {
		// 現在の時刻を計算
		sci2.SomeField = "ebata is great"
    	sci2.AnotherField = seconds

		// パブリッシュ
		json_sci2, _ := json.Marshal(sci2)
		r, err := redis.Int(conn.Do("PUBLISH", "SubClockInfo_2", json_sci2))
		if err != nil {
			panic(err)
		}
		fmt.Println(r)

		// 7秒待つ (実際は、0.05秒くらいだが、確認用に長くしている)
		time.Sleep(7000 * time.Millisecond)

		// 1秒値を増加させる
		seconds += 1 
	}
}



func person_1(person_num int, wg *sync.WaitGroup) {
	defer wg.Done()
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)

	}
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe("ClockInfo_1") // 2つに増やした

	for {	
		switch v := psc.Receive().(type) { // redisのメッセージを受けとると、ここでロックが外れる

		case redis.Message:

			switch v.Channel{

			case "ClockInfo_1":  // ブロードキャスト"ClockInfo_1"のメッセージは、こっちでキャッチ
				ci := new(Clock_Info) 
        		_ = json.Unmarshal(v.Data, &ci)
        		fmt.Println("Person_1:", person_num, "VirtualTime (ClockInfo_1):", ci.VirtualTime)

			case "SubClockInfo_2": // ブロードキャスト"SubClockInfo_2"のメッセージは、こっちでキャッチ
        		subClockData := new(SubClockInfo_2)
        		_ = json.Unmarshal(v.Data, &subClockData)
        		fmt.Println("Person_1:", person_num, "SomeField (SubClockInfo_2):", subClockData.SomeField)
         		fmt.Println("Person_1:", person_num, "AnotherField (SubClockInfo_2):", subClockData.AnotherField)


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

		case error:
			return
		}
	}

}

func person_2(person_num int, wg *sync.WaitGroup) {
	defer wg.Done()
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)

	}
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe("SubClockInfo_2") // 2つに増やした

	for {	
		switch v := psc.Receive().(type) { // redisのメッセージを受けとると、ここでロックが外れる

		case redis.Message:

			switch v.Channel{


			case "ClockInfo_1":  // ブロードキャスト"ClockInfo_1"のメッセージは、こっちでキャッチ
				ci := new(Clock_Info) 
        		_ = json.Unmarshal(v.Data, &ci)
        		fmt.Println("Person_2:", person_num, "VirtualTime (ClockInfo_1):", ci.VirtualTime)

			case "SubClockInfo_2": // ブロードキャスト"SubClockInfo_2"のメッセージは、こっちでキャッチ
        		subClockData := new(SubClockInfo_2)
        		_ = json.Unmarshal(v.Data, &subClockData)
        		fmt.Println("Person_2:", person_num, "SomeField (SubClockInfo_2):", subClockData.SomeField)
         		fmt.Println("Person_2:", person_num, "AnotherField (SubClockInfo_2):", subClockData.AnotherField)
    	}
		
		case redis.Subscription:
			fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)

		case error:
			return
		}
	}

}


func main() {

	wg := sync.WaitGroup{}

	//wg.Add(1)
	//go BaseClock(&wg)
	go BaseClock()
	go SubClock()

	for i := 0; i < 5; i++ { // 5人
		wg.Add(1)
		go person_1(i, &wg)
	}

	for i := 0; i < 5; i++ { // 5人
		wg.Add(1)
		go person_2(i, &wg)
	}


	wg.Wait()
	fmt.Println("end of ... main()")
}

ちゃんと動くみたいです。

2023,江端さんの技術メモ

"SubClockInfo_2" チャンネルから受信したデータを処理するために、新しい構造体を定義します。異なるデータ型に合わせて構造体を定義し、Unmarshal でデータを解析します。

"SubClockInfo_2" チャンネルからのメッセージを受け取るために、person 関数内の switch ステートメントを更新します。具体的には、"SubClockInfo_2" チャンネルのデータ型に合わせて処理を分岐させます。

このようにすることで、"SubClockInfo_2" チャンネルから異なる型のデータを受信し、それに応じた処理を行うことができます。異なるデータ型ごとに適切な構造体を用意し、Unmarshal でデータを解析しましょう。

// C:\Users\ebata\tomioka3B\src\others\main27.go
// 2つのクロック(goroutine)を用意して、一つのswitchで受けとれるかどうかの実験

package main

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

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


type Clock_Info struct {
	VirtualTime time.Time
	RealTime    time.Time
}

type SubClockInfo_2 struct {
    // 異なるデータ型のフィールドをここに追加
    SomeField string
    AnotherField int
}

func BaseClock() {

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

	// スタート時刻を指定
	startTime := time.Date(2023, 10, 1, 7, 0, 0, 0, time.UTC)

	// 1秒値を保持する変数
	seconds := 0

	var ci Clock_Info

	// ループを開始
	for {
		// 現在の時刻を計算
		ci.VirtualTime = startTime.Add(time.Duration(seconds) * time.Second)
		ci.RealTime = time.Now()

		// 現在の時刻を表示
		// fmt.Println("シミュレータの時刻:", ci.VirtualTime.Format("2006/01/02 15:04:05"))
		// fmt.Println("現在の時刻:", ci.RealTime.Format("2006/01/02 15:04:05")) // "2006/01/02 15:04:05"はフォーマットの形を真似るもので、内容に意味なし

		// パブリッシュ
		json_ci, _ := json.Marshal(ci)
		r, err := redis.Int(conn.Do("PUBLISH", "ClockInfo_1", json_ci))
		if err != nil {
			panic(err)
		}
		fmt.Println(r)

		// 5秒待つ (実際は、0.05秒くらいだが、確認用に長くしている)
		time.Sleep(5000 * time.Millisecond)

		// 1秒値を増加させる
		seconds++
	}
}


func SubClock() {  // 実験用に追加(時間ではなく、単なる文字列と数値を送り込むだけ)

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


	// 1秒値を保持する変数
	seconds := 0

	var sci2 SubClockInfo_2

	// ループを開始
	for {
		// 現在の時刻を計算
		sci2.SomeField = "ebata is great"
    	sci2.AnotherField = seconds

		// パブリッシュ
		json_sci2, _ := json.Marshal(sci2)
		r, err := redis.Int(conn.Do("PUBLISH", "SubClockInfo_2", json_sci2))
		if err != nil {
			panic(err)
		}
		fmt.Println(r)

		// 7秒待つ (実際は、0.05秒くらいだが、確認用に長くしている)
		time.Sleep(7000 * time.Millisecond)

		// 1秒値を増加させる
		seconds += 1 
	}
}




func person(person_num int, wg *sync.WaitGroup) {
	defer wg.Done()
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe("ClockInfo_1", "SubClockInfo_2") // 2つに増やした

	for {	
		switch v := psc.Receive().(type) { // redisのメッセージを受けとると、ここでロックが外れる

		case redis.Message:

			switch v.Channel{

			case "ClockInfo_1":  // ブロードキャスト"ClockInfo_1"のメッセージは、こっちでキャッチ
				ci := new(Clock_Info) 
        		_ = json.Unmarshal(v.Data, &ci)
        		fmt.Println("Person:", person_num, "VirtualTime (ClockInfo_1):", ci.VirtualTime)

			case "SubClockInfo_2": // ブロードキャスト"SubClockInfo_2"のメッセージは、こっちでキャッチ
        		subClockData := new(SubClockInfo_2)
        		_ = json.Unmarshal(v.Data, &subClockData)
        		fmt.Println("Person:", person_num, "SomeField (SubClockInfo_2):", subClockData.SomeField)
         		fmt.Println("Person:", person_num, "AnotherField (SubClockInfo_2):", subClockData.AnotherField)
    	}
		
		case redis.Subscription:
			fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)

		case error:
			return
		}
	}

	/*
	for {
		ci := new(Clock_Info)
		switch v := psc.Receive().(type) {
		case redis.Message:
			_ = json.Unmarshal(v.Data, &ci)
			fmt.Println("Person:", person_num, "VirtualTime:", ci.VirtualTime)
			//fmt.Println("Person:", person_num, "RealTime:", ci.RealTime)

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

		case error:
			return
		}
	}
	*/

}

func main() {

	wg := sync.WaitGroup{}

	//wg.Add(1)
	//go BaseClock(&wg)
	go BaseClock()
	go SubClock()

	for i := 0; i < 5; i++ { // 5人
		wg.Add(1)
		go person(i, &wg)
	}

	wg.Wait()
	fmt.Println("end of ... main()")
}

出力結果はこんな感じになり、期待した通りの動きをしているようです。

Person: 1 VirtualTime (ClockInfo_1): 2023-10-01 07:00:03 +0000 UTC
Person: 3 VirtualTime (ClockInfo_1): 2023-10-01 07:00:03 +0000 UTC
Person: 0 VirtualTime (ClockInfo_1): 2023-10-01 07:00:03 +0000 UTC
Person: 4 VirtualTime (ClockInfo_1): 2023-10-01 07:00:03 +0000 UTC
Person: 2 VirtualTime (ClockInfo_1): 2023-10-01 07:00:03 +0000 UTC
5
Person: 1 VirtualTime (ClockInfo_1): 2023-10-01 07:00:04 +0000 UTC
Person: 0 VirtualTime (ClockInfo_1): 2023-10-01 07:00:04 +0000 UTC
Person: 3 VirtualTime (ClockInfo_1): 2023-10-01 07:00:04 +0000 UTC
Person: 2 VirtualTime (ClockInfo_1): 2023-10-01 07:00:04 +0000 UTC
Person: 4 VirtualTime (ClockInfo_1): 2023-10-01 07:00:04 +0000 UTC
5
Person: 3 SomeField (SubClockInfo_2): ebata is great
Person: 3 AnotherField (SubClockInfo_2): 3
Person: 0 SomeField (SubClockInfo_2): ebata is great
Person: 0 AnotherField (SubClockInfo_2): 3
Person: 1 SomeField (SubClockInfo_2): ebata is great
Person: 1 AnotherField (SubClockInfo_2): 3
Person: 4 SomeField (SubClockInfo_2): ebata is great
Person: 4 AnotherField (SubClockInfo_2): 3
Person: 2 SomeField (SubClockInfo_2): ebata is great
Person: 2 AnotherField (SubClockInfo_2): 3
5
Person: 3 VirtualTime (ClockInfo_1): 2023-10-01 07:00:05 +0000 UTC
Person: 4 VirtualTime (ClockInfo_1): 2023-10-01 07:00:05 +0000 UTC
Person: 1 VirtualTime (ClockInfo_1): 2023-10-01 07:00:05 +0000 UTC
Person: 0 VirtualTime (ClockInfo_1): 2023-10-01 07:00:05 +0000 UTC
Person: 2 VirtualTime (ClockInfo_1): 2023-10-01 07:00:05 +0000 UTC
5
Person: 3 SomeField (SubClockInfo_2): ebata is great
Person: 3 AnotherField (SubClockInfo_2): 4
Person: 0 SomeField (SubClockInfo_2): ebata is great
Person: 4 SomeField (SubClockInfo_2): ebata is great
Person: 4 AnotherField (SubClockInfo_2): 4
Person: 1 SomeField (SubClockInfo_2): ebata is great
Person: 1 AnotherField (SubClockInfo_2): 4
Person: 0 AnotherField (SubClockInfo_2): 4
Person: 2 SomeField (SubClockInfo_2): ebata is great
Person: 2 AnotherField (SubClockInfo_2): 4
5

以上

2023,江端さんの技術メモ

[サーバ側] Windows10
https://iperf.fr/iperf-download.php#windows からダウンロード
インストールとかではなくて、バイナリが直接解凍される

[クライアント側] Ubuntu22.04
$ sudo apt install iperf3

使い方
[サーバ側] iperf3.exe -s
[クライアント側]iperf3.exe -c 192.168.11.167(サーバのIPアドレス等)


構成1

サーバ側の表示はこんな感じでした。

C:\Users\maoeb\iperf-3.1.3-win64>iperf3.exe -s
-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
Accepted connection from 192.168.11.125, port 33940
[  5] local 192.168.11.167 port 5201 connected to 192.168.11.125 port 33946
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-1.00   sec  8.85 MBytes  74.2 Mbits/sec
[  5]   1.00-2.00   sec  5.86 MBytes  49.0 Mbits/sec
[  5]   2.00-3.00   sec  4.79 MBytes  40.2 Mbits/sec
[  5]   3.00-4.01   sec  5.13 MBytes  42.9 Mbits/sec
[  5]   4.01-5.00   sec  5.10 MBytes  42.8 Mbits/sec
[  5]   5.00-6.00   sec  8.65 MBytes  72.8 Mbits/sec
[  5]   6.00-7.01   sec  5.84 MBytes  48.7 Mbits/sec
[  5]   7.01-8.00   sec  5.06 MBytes  42.6 Mbits/sec
[  5]   8.00-9.01   sec  5.25 MBytes  43.9 Mbits/sec
[  5]   9.01-10.00  sec  8.37 MBytes  70.6 Mbits/sec
[  5]  10.00-10.02  sec   194 KBytes  84.6 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-10.02  sec  0.00 Bytes  0.00 bits/sec                  sender
[  5]   0.00-10.02  sec  63.1 MBytes  52.8 Mbits/sec                  receiver

構成2

C:\Users\maoeb\iperf-3.1.3-win64>iperf3.exe -s
-----------------------------------------------------------
Server listening on 5201
-----------------------------------------------------------
Accepted connection from 192.168.1.15, port 39780
[  5] local 192.168.1.8 port 5201 connected to 192.168.1.15 port 39784
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-1.00   sec  11.2 MBytes  94.2 Mbits/sec
[  5]   1.00-2.00   sec  10.9 MBytes  91.2 Mbits/sec
[  5]   2.00-3.00   sec  11.3 MBytes  94.9 Mbits/sec
[  5]   3.00-4.00   sec  11.3 MBytes  94.9 Mbits/sec
[  5]   4.00-5.00   sec  11.3 MBytes  94.9 Mbits/sec
[  5]   5.00-6.00   sec  11.3 MBytes  94.9 Mbits/sec
[  5]   6.00-7.00   sec  11.3 MBytes  94.9 Mbits/sec
[  5]   7.00-8.00   sec  11.3 MBytes  94.8 Mbits/sec
[  5]   8.00-9.00   sec  11.3 MBytes  94.9 Mbits/sec
[  5]   9.00-10.00  sec  11.3 MBytes  94.9 Mbits/sec
[  5]  10.00-10.01  sec  85.5 KBytes  89.1 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-10.01  sec  0.00 Bytes  0.00 bits/sec                  sender
[  5]   0.00-10.01  sec   113 MBytes  94.5 Mbits/sec                  receiver

構成3

劣化通信環境を使う上での工夫

-----------------------------------------------------------
Accepted connection from 192.168.11.125, port 39260
[  5] local 192.168.11.167 port 5201 connected to 192.168.11.125 port 39270
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-1.01   sec  4.23 MBytes  35.0 Mbits/sec
[  5]   1.01-2.01   sec  3.69 MBytes  31.2 Mbits/sec
[  5]   2.01-3.00   sec  4.46 MBytes  37.4 Mbits/sec
[  5]   3.00-4.00   sec  4.41 MBytes  37.1 Mbits/sec
[  5]   4.00-5.00   sec  3.74 MBytes  31.4 Mbits/sec
[  5]   5.00-6.00   sec  4.13 MBytes  34.5 Mbits/sec
[  5]   6.00-7.00   sec  4.39 MBytes  36.9 Mbits/sec
[  5]   7.00-8.01   sec  4.14 MBytes  34.5 Mbits/sec
[  5]   8.01-9.00   sec  4.23 MBytes  35.7 Mbits/sec
[  5]   9.00-10.00  sec  5.12 MBytes  42.9 Mbits/sec
[  5]  10.00-10.02  sec   153 KBytes  74.3 Mbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth
[  5]   0.00-10.02  sec  0.00 Bytes  0.00 bits/sec                  sender
[  5]   0.00-10.02  sec  42.7 MBytes  35.7 Mbits/sec                  receiver


安定した、劣化環境を作るのは、これで、なかなか難しい。

2023,江端さんの技術メモ

今迄、エージェントプログラムでは、エージェントの中に内部時計(正確にはSleep())を組み込んで、エージェント間の時刻同期については、無視してきたのですが、交通シミュレーションに時刻表を入れる必要になって、無視できなくなってきました。

今や、私の十八番となったredisを使ったgolangのgoroutineで作った複数エージェントへのメッセージのブロードキャストを使って、時刻同期をするサンプルプログラムを作ってみました。

BaseClock()から、所定時間間隔で、シミュレーション共通の時刻をブロードキャストして、person()のエージェントで、その時刻情報を割り込み受信させることにしました。

ブロードキャストによる遅延や割り込み取りこぼしが心配ではありますが、これまでの経験上redisのブロードキャストは十分に高速なようなので、多分大丈夫だと思います。

// C:\Users\ebata\tomioka3B\src\others\main26.go

package main

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

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

var Clock1 chan interface{}

type Clock_Info struct {
	VirtualTime time.Time
	RealTime    time.Time
}

func BaseClock(wg *sync.WaitGroup) {
	defer wg.Done()

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

	// スタート時刻を指定
	startTime := time.Date(2023, 10, 1, 7, 0, 0, 0, time.UTC)

	// 1秒値を保持する変数
	seconds := 0

	var ci Clock_Info

	// ループを開始
	for {
		// 現在の時刻を計算
		ci.VirtualTime = startTime.Add(time.Duration(seconds) * time.Second)
		ci.RealTime = time.Now()

		// 現在の時刻を表示
		fmt.Println("シミュレータの時刻:", ci.VirtualTime.Format("2006/01/02 15:04:05"))
		fmt.Println("現在の時刻:", ci.RealTime.Format("2006/01/02 15:04:05")) // "2006/01/02 15:04:05"はフォーマットの形を真似るもので、内容に意味なし

		// パブリッシュ
		json_ci, _ := json.Marshal(ci)
		r, err := redis.Int(conn.Do("PUBLISH", "ClockInfo_1", json_ci))
		if err != nil {
			panic(err)
		}
		fmt.Println(r)

		// 5秒待つ (実際は、0.05秒くらいだが、確認用に長くしている)
		time.Sleep(5000 * time.Millisecond)

		// 1秒値を増加させる
		seconds++
	}
}

func person(person_num int, wg *sync.WaitGroup) {
	defer wg.Done()
	// 接続
	conn, err := redis.Dial("tcp", "localhost:6379")
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	psc := redis.PubSubConn{Conn: conn}
	psc.Subscribe("ClockInfo_1")

	for {
		ci := new(Clock_Info)
		switch v := psc.Receive().(type) {
		case redis.Message:
			_ = json.Unmarshal(v.Data, &ci)
			fmt.Println("Person:", person_num, "VirtualTime:", ci.VirtualTime)
			fmt.Println("Person:", person_num, "RealTime:", ci.RealTime)

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

		case error:
			return
		}
	}
}

func main() {

	wg := sync.WaitGroup{}

	wg.Add(1)
	go BaseClock(&wg)

	for i := 0; i < 10; i++ { // 10人
		wg.Add(1)
		go person(i, &wg)
	}

	wg.Wait()
	fmt.Println("end of ... main()")
}

 

 

2023,江端さんの技術メモ

pgr_dijkstra()で、ダイクストラの順番を壊さずにルートの座標を得る方法(getDijkstraPath())

ノードの切断を発生させず、ノード10~ノード20と、ノード30~ノード40を回避するダイクストラ計算を行うPostGISのクエリー

をベースとしたGo言語のプログラムになります。

// C:\Users\ebata\tomioka3B\src\others\main23.go
package main

import (
	"database/sql"
	"fmt"
	"log"
	"m/src/ldarp"
	"strconv"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres",
		"user=postgres password=password host=192.168.0.23 port=15432 dbname=tomioka_db_c sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	/*
		以下のldarp.LocInfoの内容は、
		type LocInfo struct {
			Lon    float64
			Lat    float64
			Source int
		}
		となっています。
		このプログラムファイルでは、上記を定義して、ldarp.LocInfoを、LocInfoと変換すれば動きます

		ただ、前提とするpostgresqlのpostGISが取り扱うデータベースは、
		mapconfig_for_cars.xml をベースとして構築しているものになりますので、ご留意下さい
		https://github.com/pgRouting/osm2pgrouting/blob/main/mapconfig_for_cars.xml
	*/

	var a_Point, b_Point ldarp.LocInfo

	a_Point.Source = 1  // 出発ノード
	b_Point.Source = 50 // 到着ノード

	var del_start1, del_end1, del_start2, del_end2 ldarp.LocInfo
	//var del_start3, del_end3 ldarp.LocInfo

	del_start1.Source = 10 // 1つ目の無効としたいノード番号(開始番号)
	del_end1.Source = 20   // 1つ目の無効としたいノード番号(終了番号)
	del_start2.Source = 30 // 2つ目の無効としたいノード番号(開始番号)
	del_end2.Source = 40   // 2つ目の無効としたいノード番号(開始番号)
	//del_start3.Source = 30
	//del_end3.Source = 35

	array1 := []ldarp.LocInfo{del_start1, del_end1} // 上記の無効としたいノード番号を登録する
	array2 := []ldarp.LocInfo{del_start2, del_end2} // 上記の無効としたいノード番号を登録する
	//array3 := []ldarp.LocInfo{del_start3, del_end3}

	//delSourceArray := [][]ldarp.LocInfo{array1, array2, array3}
	delSourceArray := [][]ldarp.LocInfo{array1, array2} // 登録したノード番号をarraysの変数に纏める
	//delSourceArray = [][]ldarp.LocInfo{}

	// arrays変数への挿入
	//for _, arr := range arrays {
	//	delSourceArray = append(delSourceArray, arr)
	//}

	// fmt.Println(delSourceArray)

	path, dist := getDijkstraPath(db, a_Point, b_Point, delSourceArray)

	fmt.Println("path:", path)
	fmt.Println("dist:", dist)

}

// 江端再修正版(2023/09/29)
/*
出発ノードと到着ノードと、通過したくないノード(無効したいノード)を登録して、ダイクストラ計算によるルートを選ぶ
*/

func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal ldarp.LocInfo, delSourceArray [][]ldarp.LocInfo) ([]ldarp.LocInfo, float64) {
	log.Println("getDijkstraPath", locInfoStart, locInfoGoal)

	sql_str := "" // SQL文を作る文字列の初期値

	// delSourceArray の解析
	fmt.Println("delSourceArray:", delSourceArray)

	if len(delSourceArray) != 0 { // delSourceArrayに要素が入っていれば

		// forとrangeを使用して各要素のSource値を取得
		sql_str = "WHERE "

		for _, arr := range delSourceArray {
			/*
				for _, loc := range arr {
					fmt.Printf("Sourceの値: %d\n", loc.Source)
				}
			*/
			fmt.Println(arr[0].Source, arr[1].Source)
			//str += "( source NOT BETWEEN " + arr[0].Source + " AND " + arr[1].Source + " AND target NOT BETWEEN " + arr[0].Source + " AND " + arr[1].Source + ")"
			sql_str += "(source NOT BETWEEN " + strconv.Itoa(arr[0].Source) + " AND " + strconv.Itoa(arr[1].Source) + " AND target NOT BETWEEN " + strconv.Itoa(arr[0].Source) + " AND " + strconv.Itoa(arr[1].Source) + ") AND "
			//fmt.Println(str)
		}
		strlen := len(sql_str)
		sql_str = sql_str[:strlen-5] // ラストの" AND "の5文字を削除する
		//fmt.Println(str)
	}

	sql_str = "SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, cost, reverse_cost FROM ways " + sql_str + "', $1::bigint , $2::bigint , directed:=false ) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq"

	//fmt.Println(sql_str)

	var path []ldarp.LocInfo // 経路 (返り値の一つ目)
	var totalDistanceKm float64

	rowsDijkstra, errDijkstra := dbMap.Query(
		//"SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways', $1::bigint , $2::bigint , directed:=false ) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq",
		//"SELECT seq,source, target, x1, y1, x2, y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways WHERE (source NOT BETWEEN 10 AND 20 AND target NOT BETWEEN 10 AND 20) AND (source NOT BETWEEN 30 AND 40 AND target NOT BETWEEN 30 AND 40)', $1::bigint , $2::bigint , directed:=false ) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq",
		sql_str,
		locInfoStart.Source,
		locInfoGoal.Source)

	if errDijkstra != nil {
		log.Fatal(errDijkstra)
	}
	defer rowsDijkstra.Close()

	var agg_cost float64

	var loc ldarp.LocInfo
	var x1, y1, x2, y2 float64
	var seq int
	var target int
	var source int

	isFirstCheck := true
	isSourceCheck := true

	for rowsDijkstra.Next() {

		// まずnodeを読む
		if err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost); err != nil {
			fmt.Println(err)
		}

		// 最初の1回だけチェックのために入る これについては、https://wp.kobore.net/江端さんの技術メモ/post-7668/を参照のこと
		// もし rowsDijkstra.Scanで最初のsource値を読みとり、locInfoStart.Source の値と同じであれば、x1,y1をベースとして、異なる値であれば、x2,y2をベースとする

		if isFirstCheck {
			if source == locInfoStart.Source {
				isSourceCheck = true // x1, y1をベースとする処理になる
			} else {
				isSourceCheck = false // x2,y2をベースとする処理になる
			}
			isFirstCheck = false // 最初の1回をチェックすることで、2回目はこのループには入らなくなる
		}

		//var loc ldarp.LocInfo

		if isSourceCheck { // x1, y1をベースとする処理
			loc.Source = source
			loc.Lon = x1
			loc.Lat = y1
		} else { // x2,y2をベースとする処理
			loc.Source = target
			loc.Lon = x2
			loc.Lat = y2
		}
		path = append(path, loc)
	}

	// ラストノードだけはsourceとtargetを引っくり返す
	if isSourceCheck { // x1, y1をベースとする処理
		loc.Source = target
		loc.Lon = x2
		loc.Lat = y2
	} else { // x2,y2をベースとする処理
		loc.Source = source
		loc.Lon = x1
		loc.Lat = y1
	}

	path = append(path, loc)

	totalDistanceKm = agg_cost / 1000.0
	return path, totalDistanceKm
}

2023,江端さんの技術メモ

C:\Users\ebata\tomioka3B\src\others\main21.goでバトル中 

tomioka_db_c=# SELECT seq, node, edge, source, target, b.cost FROM pgr_dijkstra('SELECT gid as id, source, target, cost, reverse_cost FROM ways WHERE (source NOT BETWEEN 10 AND 20 AND target NOT BETWEEN 10 AND 20) AND (source NOT BETWEEN 30 AND 40 AND target NOT BETWEEN 30 AND 40)',1, 50, directed := false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq;

seq | node | edge | source | target | cost
-----+------+------+--------+--------+------------------------
1 | 1 | 2 | 1 | 2 | 8.518550262944415e-05
2 | 2 | 3 | 2 | 3 | 0.0009420518456308501
3 | 3 | 4 | 3 | 4 | 0.00026662995399998605
4 | 4 | 5 | 4 | 5 | 0.00021389117484400878
5 | 5 | 6 | 5 | 6 | 4.656362881781325e-05
6 | 6 | 7 | 6 | 7 | 0.00013184236987589488
7 | 7 | 8 | 7 | 8 | 6.438782425766514e-05
8 | 8 | 1254 | 8 | 897 | 1.2290752319372595e-05
9 | 897 | 1380 | 897 | 969 | 0.00038637624148597305
10 | 969 | 1938 | 969 | 1389 | 2.250777644883257e-05
11 | 1389 | 694 | 1389 | 542 | 0.0002978010745492113
12 | 542 | 1251 | 542 | 895 | 0.00015951582993150323
13 | 895 | 1815 | 895 | 1305 | 0.0001303909506021454
14 | 1305 | 1021 | 1305 | 753 | 3.9356066874244885e-05
15 | 753 | 1381 | 753 | 971 | 0.0002850072862639029
16 | 971 | 1382 | 976 | 971 | 0.001276712172995213
17 | 976 | 1388 | 977 | 976 | 0.0004267486614026622
18 | 977 | 1389 | 986 | 977 | 0.0006757336753520132
19 | 986 | 1404 | 906 | 986 | 0.0003707891314505199
20 | 906 | 1275 | 906 | 907 | 0.0001959266189093194
21 | 907 | 1277 | 907 | 908 | 9.052734394018332e-05
22 | 908 | 1921 | 908 | 1376 | 5.400925846418281e-05
23 | 1376 | 1279 | 1376 | 909 | 1.8504323815069496e-05
24 | 909 | 1057 | 909 | 773 | 6.840328939622445e-05
25 | 773 | 1069 | 773 | 781 | 0.000535513050350553
26 | 781 | 1071 | 781 | 784 | 0.00014276168953525706
27 | 784 | 1087 | 784 | 793 | 0.0004430493335842619
28 | 793 | 1682 | 793 | 1192 | 7.9895243915049e-05
29 | 1192 | 1111 | 1192 | 808 | 0.0005876979547862221
30 | 808 | 1125 | 808 | 816 | 0.00047564125136792903
31 | 816 | 51 | 816 | 49 | 3.10328360877585e-05
32 | 49 | 52 | 49 | 50 | 0.00028187114581170033

一応、QGISで調べて、ノード切れが発生していないことは確認しました。


新規に作ったノード182~413を、ちゃんと無視できるかの実験。ベースは以下のメモの内容。

pgr_dijkstra()で、ダイクストラの順番を壊さずにルートの座標を得る方法(getDijkstraPath())

■無視前

tomioka_db_c=# SELECT seq, source, target, x1, y1, x2, y2 FROM pgr_dijkstra('SELECT gid as id, source, target, cost, reverse_cost FROM ways',1036, 1320, directed := false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq;
seq | source | target | x1 | y1 | x2 | y2
-----+--------+--------+-----------------+----------------+-----------------+----------------
1 | 1036 | 182 | 139.6295688 | 35.3660511 | 139.62956342765 | 35.36605302299
2 | 182 | 183 | 139.62956342765 | 35.36605302299 | 139.62952386387 | 35.36602637499
3 | 183 | 184 | 139.62952386387 | 35.36602637499 | 139.62949515388 | 35.36599335049
4 | 184 | 185 | 139.62949515388 | 35.36599335049 | 139.62949783814 | 35.3658805722
5 | 185 | 186 | 139.62949783814 | 35.3658805722 | 139.629505074 | 35.36583679324
6 | 186 | 187 | 139.629505074 | 35.36583679324 | 139.62955514143 | 35.36580196041
7 | 187 | 188 | 139.62955514143 | 35.36580196041 | 139.62972133265 | 35.36574533322
8 | 188 | 189 | 139.62972133265 | 35.36574533322 | 139.62969285571 | 35.3656615823
9 | 189 | 190 | 139.62969285571 | 35.3656615823 | 139.62929978548 | 35.36461811112
10 | 190 | 191 | 139.62929978548 | 35.36461811112 | 139.62923349573 | 35.3644776355
11 | 191 | 192 | 139.62923349573 | 35.3644776355 | 139.62887730502 | 35.36401128513
12 | 192 | 193 | 139.62887730502 | 35.36401128513 | 139.62881288258 | 35.36394808968
13 | 193 | 194 | 139.62881288258 | 35.36394808968 | 139.62878113819 | 35.36393552673
14 | 194 | 195 | 139.62878113819 | 35.36393552673 | 139.62874285819 | 35.36391763403
15 | 195 | 196 | 139.62874285819 | 35.36391763403 | 139.62711549003 | 35.36370406459
16 | 196 | 1320 | 139.62711549003 | 35.36370406459 | 139.6270944 | 35.3636816
(16 rows)

■無視後

tomioka_db_c=# SELECT seq, source, target, x1, y1, x2, y2 FROM pgr_dijkstra('SELECT gid as id, source, target, cost, reverse_cost FROM ways WHERE (source NOT BETWEEN 182 AND 413 AND target NOT BETWEEN 182 AND 413) AND (source NOT BETWEEN 182 AND 413 AND target NOT BETWEEN 182 AND 413)',1036, 1320, directed := false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq;
seq | source | target | x1 | y1 | x2 | y2
-----+--------+--------+-------------+------------+-------------+------------
1 | 1034 | 1036 | 139.6297237 | 35.365746 | 139.6295688 | 35.3660511
2 | 1034 | 1033 | 139.6297237 | 35.365746 | 139.6297007 | 35.3656671
3 | 1033 | 1325 | 139.6297007 | 35.3656671 | 139.6287884 | 35.3639297
4 | 1325 | 1320 | 139.6287884 | 35.3639297 | 139.6270944 | 35.3636816

うん、これを見ている限りは、大丈夫そうに見える。

 

2023,江端さんの技術メモ

■通常のバスルートを作ったが、表示ノードが停留所のみとなり、シミュレータが不自然になる問題の解決法
   → 強制的にタグを付ける

mapconfig_for_cars_rail_cart_bus.xml

の変更も忘れないこと。

Keyword : osm, tag, node, ダイクストラ、

 

2023,江端さんの技術メモ

QGISでノード番号のある場所を見つける方法が分からなくて困っていましたが、ようやく方法を見つけましたので、メモを残します。

この場合、180以下のノード番号が表示されていますが、特定の番号を指定したければ、(例えば、id=3)と入力すれば、そのノードだけが表示されます。

以上