// G:\home\ebata\hakata\src\others\main26\main.c
/*
このプログラムは、PostGISを使用したPostgreSQLデータベースに対して、ランダムウォークを実行し、その経路をCSVファイルに記録するものです。プログラムの概要を以下に説明します。
■プログラムの目的
出発ノードと目的ノード(天神駅に近いノード)を設定し、データベース上のノード間をランダムに移動しながら、最終的に目的ノードに到達する経路を記録します。
経路をCSVファイルに保存し、各ノードの緯度、経度情報を含めます。
■主な機能と処理の流れ
1.データベースへの接続
sql.Openを使って、PostgreSQLデータベースに接続します。接続情報はconnStr変数で定義されます。
2。出発ノードと目的ノードの設定
startNodeとして、仮に32758を設定し、goalNodeとして天神駅のノードIDである40922を設定しています。
3.CSVファイルの作成
os.Createでファイルを作成し、CSV形式で書き込むための準備をします。
4.ランダムウォークの実行
performRandomWalk関数内で、出発ノードから目的ノードへと移動を行います。
各ノードの緯度と経度を取得し、CSVファイルに記録します。
5.ノード間の移動の処理
getClosestConnectedNode関数を使って、現在のノードから接続されているノードを取得し、目的地に最も近いノードを選択します。
選択の基準として、訪問回数が少ないノードを優先し、ランダム要素を加えます(袋小路の回避)。
6.経路の記録
移動したノードの情報を、performRandomWalk関数内でCSVファイルに記録します。ノードID、緯度、経度が書き込まれます。
7.座標の取得
getNodeCoordinates関数で、ノードIDに対応する緯度と経度をPostGISの関数ST_YとST_Xを使用して取得します。
8.2点間の距離の計算
calculateDistance関数で、2つの座標間の距離を計算します。地球の半径を用いて、Haversineの公式で計算しています。
■プログラムの特徴
1. 訪問回数の管理: 同じノードを何度も訪問しないように、各ノードの訪問回数を管理しています(訪問回数が多いノードは回避します)。
2. ランダム要素の導入: 最短経路だけではなく、一定の確率で他の接続ノードを選択し、袋小路を避けるように工夫しています。
3. バックトラッキング: 袋小路に入った場合に、直前のノードに戻るバックトラッキングの処理を行っています。
■プログラムの全体の流れ
1.データベース接続を確立。
2.出発ノードと目的ノードを設定。
3.CSVファイルを作成し、ヘッダーを記述。
4.ランダムウォークを実行し、ノード間を移動。
5.各ノードで緯度・経度を取得し、CSVファイルに書き込む。
6.目的ノードに到達するまで、最も適切な接続ノードを選択し続ける。
このプログラムは、ランダムウォークアルゴリズムをPostGISとPostgreSQLの地理情報を活用して実現しており、目的地に到達するまでの経路を記録する機能を持っています。
*/
package main
import (
"database/sql"
"encoding/csv"
"fmt"
"log"
"math"
"math/rand"
"os"
_ "github.com/lib/pq"
)
const (
connStr = "user=postgres password=password host=127.0.0.1 port=15432 dbname=hakata_db sslmode=disable"
)
func main() {
// PostgreSQLデータベースへの接続
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 出発ノードを設定(天神駅の最も近いノードを選択するなど)
startNode := 32758 // 例として、初期ノードIDを設定
goalNode := 40922 // 目的ノードIDを天神駅に設定
// CSVファイルを作成
csvFile, err := os.Create("random_walk_result.csv")
if err != nil {
log.Fatal(err)
}
defer csvFile.Close()
writer := csv.NewWriter(csvFile)
defer writer.Flush()
// CSVのヘッダーを作成
err = writer.Write([]string{"Node", "Latitude", "Longitude"})
if err != nil {
log.Fatal(err)
}
// ランダムウォークの実行
err = performRandomWalk(db, startNode, goalNode, writer)
if err != nil {
log.Fatal(err)
}
fmt.Println("Random walk completed and saved to CSV.")
}
// performRandomWalk ランダムウォークを実行し、結果をCSVに保存
func performRandomWalk(db *sql.DB, startNode, goalNode int, writer *csv.Writer) error {
currentNode := startNode
visited := make(map[int]int) // ノードの訪問回数を追跡
pathStack := []int{} // バックトラッキング用のスタック
for currentNode != goalNode {
// 現在のノードの緯度と経度を取得し、CSVに書き込み
lat, lon, err := getNodeCoordinates(db, currentNode)
if err != nil {
return err
}
fmt.Printf("Node %d: Latitude: %f, Longitude: %f\n", currentNode, lat, lon)
err = writer.Write([]string{
fmt.Sprintf("%d", currentNode),
fmt.Sprintf("%f", lat),
fmt.Sprintf("%f", lon),
})
if err != nil {
return err
}
writer.Flush()
// 現在のノードを訪問済みとして記録
visited[currentNode]++
// 次のノードを取得(目的地に近づくノードを優先)
nextNode, err := getClosestConnectedNode(db, currentNode, goalNode, visited)
if err != nil || visited[nextNode] > 2 { // 訪問回数が多い場合バックトラック
if len(pathStack) > 0 {
// スタックから一つ戻る
fmt.Printf("Backtracking from Node %d\n", currentNode)
currentNode = pathStack[len(pathStack)-1]
pathStack = pathStack[:len(pathStack)-1]
continue
}
return fmt.Errorf("no viable path found")
}
// 次のノードをスタックに追加
pathStack = append(pathStack, currentNode)
currentNode = nextNode
}
// ゴールノードの座標をCSVに保存
lat, lon, err := getNodeCoordinates(db, goalNode)
if err != nil {
return err
}
fmt.Printf("Arrived at Goal Node %d: Latitude: %f, Longitude: %f\n", goalNode, lat, lon)
err = writer.Write([]string{
fmt.Sprintf("%d", goalNode),
fmt.Sprintf("%f", lat),
fmt.Sprintf("%f", lon),
})
if err != nil {
return err
}
writer.Flush()
return nil
}
// getClosestConnectedNode 現在のノードから接続されているノードのうち、目的地に最も近いノードを取得
func getClosestConnectedNode(db *sql.DB, currentNode, goalNode int, visited map[int]int) (int, error) {
// 現在のノードから接続されているノードを取得
query := `
SELECT target
FROM ways
WHERE source = $1
UNION
SELECT source
FROM ways
WHERE target = $1;
`
rows, err := db.Query(query, currentNode)
if err != nil {
return 0, err
}
defer rows.Close()
// 接続ノードのリストを作成
type NodeDistance struct {
ID int
Distance float64
}
var connectedNodes []NodeDistance
for rows.Next() {
var node int
if err := rows.Scan(&node); err != nil {
return 0, err
}
// ノードの座標を取得
lat, lon, err := getNodeCoordinates(db, node)
if err != nil {
return 0, err
}
// 目的地との距離を計算
goalLat, goalLon, err := getNodeCoordinates(db, goalNode)
if err != nil {
return 0, err
}
distance := calculateDistance(lat, lon, goalLat, goalLon)
// 訪問回数が少ないノードを優先
if visited[node] < 3 {
connectedNodes = append(connectedNodes, NodeDistance{
ID: node,
Distance: distance,
})
}
}
if len(connectedNodes) == 0 {
return 0, fmt.Errorf("no connected nodes found")
}
// 目的地に最も近いノードを優先して選択
var closestNode NodeDistance
minDistance := math.MaxFloat64
for _, node := range connectedNodes {
// 最も目的地に近いノードを選択
if node.Distance < minDistance {
closestNode = node
minDistance = node.Distance
}
}
// ランダム選択の要素を追加(袋小路を避けるため)
if len(connectedNodes) > 1 && rand.Float64() < 0.2 {
return connectedNodes[rand.Intn(len(connectedNodes))].ID, nil
}
return closestNode.ID, nil
}
// getNodeCoordinates ノードIDに対応する緯度と経度を取得
func getNodeCoordinates(db *sql.DB, nodeID int) (float64, float64, error) {
var lat, lon float64
query := `
SELECT ST_Y(the_geom) AS lat, ST_X(the_geom) AS lon
FROM ways_vertices_pgr
WHERE id = $1;
`
err := db.QueryRow(query, nodeID).Scan(&lat, &lon)
if err != nil {
return 0, 0, err
}
return lat, lon, nil
}
// calculateDistance 2つの座標間の距離を計算
func calculateDistance(lat1, lon1, lat2, lon2 float64) float64 {
rad := func(deg float64) float64 {
return deg * math.Pi / 180
}
lat1Rad, lon1Rad := rad(lat1), rad(lon1)
lat2Rad, lon2Rad := rad(lat2), rad(lon2)
dlat := lat2Rad - lat1Rad
dlon := lon2Rad - lon1Rad
a := math.Sin(dlat/2)*math.Sin(dlat/2) + math.Cos(lat1Rad)*math.Cos(lat2Rad)*math.Sin(dlon/2)*math.Sin(dlon/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
const EarthRadius = 6371e3 // 地球の半径(メートル)
return EarthRadius * c
}