こぼれネット

ランダムウォークをpostGISで実現できないかの検討(ちゃんと動いているので、これでいいんじゃないかな?)

//  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
}

モバイルバージョンを終了