2024,江端さんの技術メモ

※fastapiの場合、スレッドでは上手くコンロールできない(らしい)ので、プロセス単位で管理します
(1)execute-commandというAPIから、pingを行う回数Xをjson形式入力して、"ping -n X kobore.net"を起動します。返り値はプロセス番号(pid)がjson形式で戻ります。
curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"5\"}" http://localhost:8000/execute-command
(2)terminate-processというAPIから、上記のpidをjson形式で入力して、プロセスを強制終了します。返り値は、この成否(1/-1)がjson形式で戻ります。
curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"10164\"}" http://localhost:8000/terminate-process
(3)以下のファイルをtest.pyとして、uvicorn test:app --host 0.0.0.0 --reload を投入してfastapiサーバを起動します。
# curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"5\"}" http://localhost:8000/execute-command
# curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"10164\"}" http://localhost:8000/terminate-process

import subprocess
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ExecuteRequest(BaseModel):
    count: int  # pingコマンドの実行回数を指定

class TerminateRequest(BaseModel):
    pid: int  # 終了させるプロセスのPID

# 実行中のプロセスを格納する辞書
running_processes = {}

@app.post("/execute-command")
def execute_command(request: ExecuteRequest):
    count = request.count

    try:
        command = f"ping -n {count} kobore.net"  # pingコマンドの回数をcountに指定
        # コマンドを非同期で実行し、プロセスを取得
        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        pid = process.pid  # プロセスのPIDを取得
        running_processes[pid] = process

        return {"pid": pid}  # PIDのみを返す
    except Exception as e:
        return {"message": f"コマンドの実行中にエラーが発生しました: {str(e)}"}


@app.post("/terminate-process")
def terminate_process(request: TerminateRequest):
    pid = request.pid
    print("pid in terminate-process", pid)

    try:
        # プロセスを取得し、終了させる
        process = running_processes.get(pid)

        process.terminate()  # プロセスを終了させる(SIGTERMを送信)
        process.wait()
        del running_processes[pid]  # プロセスを辞書から削除

        # 成功の場合は1を返す
        return {"status": 1}
    except Exception as e:
        return {"status": -1}  # 失敗の場合は-1を返す

if __name__ == "__main__":
    # FastAPIサーバーを開始
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

出力結果
C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"100\"}" http://localhost:8000/execute-command
{"pid":1784}
C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"1784\"}" http://localhost:8000/terminate-process
{"status":1}
C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"1784\"}" http://localhost:8000/terminate-process
{"status":-1}


起動したプロセスを監視して、プロセスが予定通り/突然停止した場合、それを通知する仕組みを追加しました。

# curl -X POST -H "Content-Type: application/json" -d "{\"count\": \"5\"}" http://localhost:8000/execute-command
# curl -X POST -H "Content-Type: application/json" -d "{\"pid\": \"10164\"}" http://localhost:8000/terminate-process
# C:\Users\ebata\fastapi7>uvicorn test:app --host 0.0.0.0 --reload

import subprocess
import os
import time
import multiprocessing  # multiprocessingモジュールを追加
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class ExecuteRequest(BaseModel):
    count: int  # pingコマンドの実行回数を指定

class TerminateRequest(BaseModel):
    pid: int  # 終了させるプロセスのPID

# 実行中のプロセスを格納する辞書
running_processes = {}
process_monitor_processes = {}

@app.post("/execute-command")
def execute_command(request: ExecuteRequest):
    count = request.count

    try:
        command = f"ping -n {count} kobore.net"  # pingコマンドの回数をcountに指定
        # コマンドを非同期で実行し、プロセスを取得
        process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        pid = process.pid  # プロセスのPIDを取得
        running_processes[pid] = process

        # プロセスを監視するプロセスを起動
        monitor_process = multiprocessing.Process(target=monitor_process_status, args=(pid,))
        monitor_process.start()
        process_monitor_processes[pid] = monitor_process

        return {"pid": pid}  # PIDのみを返す
    except Exception as e:
        return {"message": f"コマンドの実行中にエラーが発生しました: {str(e)}"}


@app.post("/terminate-process")
def terminate_process(request: TerminateRequest):
    pid = request.pid
    print("pid in terminate-process", pid)

    try:
        # プロセスを取得し、終了させる
        process = running_processes.get(pid)

        process.terminate()  # プロセスを終了させる(SIGTERMを送信)
        process.wait()
        del running_processes[pid]  # プロセスを辞書から削除

        # 成功の場合は1を返す
        return {"status": 1}
    except Exception as e:
        return {"status": -1}  # 失敗の場合は-1を返す

def monitor_process_status(pid):
    while True:
        if not is_process_running(pid):
            # プロセスが存在しない場合
            # メッセージを生成して出力(または送信)
            message = {
                "status": "Process Disappeared",
                "pid": pid
            }
            print("Process Disappeared:", message)

            # プロセス監視プロセスを停止
            ### del process_monitor_processes[pid]
            break

        # 一定の待機時間を設定して監視を継続
        time.sleep(10)  # 10秒ごとに監視

def is_process_running(pid):
    #try:
    #    os.kill(pid, 0)  # PIDを使ってプロセスにシグナルを送信し、存在を確認
    #    return True
    #except OSError:
    #    return False
    try:
        # ここの部分Windows特有のやりかたなので、後で、例の、os.kill(pid,0)を試してみること
        # tasklistコマンドを実行してプロセス一覧を取得
        result = subprocess.check_output(["tasklist", "/fi", f"PID eq {pid}"], universal_newlines=True)

        # 結果から指定したPIDの行を検索
        lines = result.splitlines()
        for line in lines:
            if f"{pid}" in line:
                return True

        # 指定したPIDが見つからない場合
        # ここに、停止時のメッセージ送信を組み込めばO.K.のはず   

        return False
    except Exception as e:
        return False





if __name__ == "__main__":
    # FastAPIサーバーを開始
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

2024,江端さんの技術メモ

指定されたコマンド "ping -100 kobore.net" をFastAPIのエンドポイントから実行し、それが終了したらAPIを正常に終了させるコードは以下のようになります:

test.py

import subprocess
import threading
import os
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class CommandRequest(BaseModel):
    command: str

@app.post("/execute-command")
def execute_command(request: CommandRequest):
    command = request.command
    # コマンドを非同期で実行
    execution_thread = threading.Thread(target=execute_command_async, args=(command,))
    execution_thread.start()
    return {"message": f"コマンド '{command}' の実行を開始しました"}

def execute_command_async(command):
    process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    process.wait()  # コマンドの終了を待つ
    print(f"コマンド '{command}' の実行が終了しました")
    # os._exit(0)  # FastAPIサーバーを終了させる

if __name__ == "__main__":
    # FastAPIサーバーを開始
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

$ uvicorn test:app --host 0.0.0.0 --reload

 

C:\Users\ebata\fastapi7>curl -X POST -H "Content-Type: application/json" -d "{\"command\": \"ping -n 20 kobore.net\"}" http://localhost:8000/execute-command

2024,江端さんの技術メモ

運行情報(ダイヤグラム)を反映した最短時間で到着する計算方法は、通常のダイクストラ法を拡張して実行することができます。以下は、ダイヤ情報を考慮する最短到着時間を計算する一般的なアプローチです:

  1. グラフの作成:
    • ダイヤグラムに基づいて、駅間の接続を表すグラフを作成します。各エッジ(駅間の接続)には、所要時間が含まれます。
    • グラフのノードは駅を表し、エッジの重みは駅間の移動にかかる時間です。
    • 通常のダイクストラ法と同様に、出発駅を始点としてグラフを探索します。
  2. ノードの拡張と更新:
    • 通常のダイクストラ法と同様に、出発駅から各駅への最短到着時間を記録するデータ構造を使用します。初期状態では、出発駅の最短到着時間を0に設定し、他の駅は無限大(無効値)とします。
    • プライオリティキューを使用して、最短到着時間が最小の駅を選択します。
  3. ダイヤ情報の適用:
    • 選択した駅から出発するエッジを調べ、ダイヤ情報を考慮して最短到着時間を計算します。
    • ダイヤ情報には、駅への到着時間や運行間隔などが含まれます。現在の到着時間とダイヤ情報を使用して、次の駅への最短到着時間を計算します。
    • 新しい到着時間が現在の最短到着時間よりも短い場合、その駅の最短到着時間を更新します。
  4. プライオリティキューから次の最短到着時間の駅を選択し、ステップ3を繰り返します。目的の駅に到達した場合、計算を終了します。
  5. 最短到着時間を使用して、目的の駅への最短経路を復元します。

このアプローチを使用すると、ダイヤ情報を反映した最短到着時間を計算できます。ダイヤ情報は、駅への到着時間や運行間隔を正確に取得し、計算に組み込む必要があります。また、プライオリティキューの実装や適切なデータ構造の設計も重要です。

2024,江端さんの技術メモ

/* 
   簡易バスダイヤ作成プログラム    c:\users\ebata\dummy1.go

   (1)バス路線があり、5つの停留所("A0", "A1", "A2", "A3", "A4", "A5")があります。
   (2)このバスは始点から運行を開始し、路線の終点でで一定時間停車した後、再び逆方向に運行を開始します。
   (3)バスは朝6時に出発して、5分単位で次の停留所で停止し、終端で10分間停止します。
   (4)これを3往復するものとします。

*/



package main

import (
	"fmt"
	"strings" // strings パッケージをインポート
	"time"
)

// バスのダイヤグラム
type BusSchedule struct {
	Route             []string             // 停留所のリスト
	DepartureTime     time.Time            // 出発時刻
	ArrivalTimeStops  map[string][]string // 各停留所の到着時刻
	ArrivalTimeRounds int                  // 往復回数
}

// バスのダイヤグラムを生成する関数
func GenerateBusSchedule(route []string, departureTime time.Time, numRoundTrips int) *BusSchedule {
	schedule := &BusSchedule{
		Route:             route,
		DepartureTime:     departureTime,
		ArrivalTimeStops:  make(map[string][]string),
		ArrivalTimeRounds: numRoundTrips,
	}

	currentTime := departureTime
	reverse := false // 逆向き運行を切り替えるフラグ

	for round := 0; round < numRoundTrips; round++ {
		routeOrder := make([]string, 0, len(route)*2-1)

		if reverse {
			// 逆向き運行の場合、終点から始点に戻る
			for i := len(route) - 1; i >= 0; i-- {
				stop := route[i]
				arrivalTime := currentTime.Format("15:04")
				schedule.ArrivalTimeStops[stop] = append(schedule.ArrivalTimeStops[stop], arrivalTime)
				routeOrder = append(routeOrder, fmt.Sprintf("%s(%d): %s", stop, len(schedule.ArrivalTimeStops[stop]), arrivalTime))
				if i > 0 {
					currentTime = currentTime.Add(5 * time.Minute)
				}
			}
			reverse = false
		} else {
			// 正向き運行の場合、始点から終点に向かう
			for i := 0; i < len(route); i++ {
				stop := route[i]
				arrivalTime := currentTime.Format("15:04")
				schedule.ArrivalTimeStops[stop] = append(schedule.ArrivalTimeStops[stop], arrivalTime)
				routeOrder = append(routeOrder, fmt.Sprintf("%s(%d): %s", stop, len(schedule.ArrivalTimeStops[stop]), arrivalTime))
				if i < len(route)-1 {
					currentTime = currentTime.Add(5 * time.Minute)
				}
			}
			reverse = true
		}

		fmt.Println(strings.Join(routeOrder, "->"))
		currentTime = currentTime.Add(10 * time.Minute) // 終点での停止時間
	}

	return schedule
}

func main() {
	route := []string{"A0", "A1", "A2", "A3", "A4", "A5"}
	departureTime := time.Date(2024, 1, 6, 6, 0, 0, 0, time.UTC)
	numRoundTrips := 3

	schedule := GenerateBusSchedule(route, departureTime, numRoundTrips)

	// routeOrder を表示
	fmt.Println("routeOrder:")
	for _, stop := range schedule.Route {
		fmt.Printf("%s:\n", stop)
		for i, arrivalTime := range schedule.ArrivalTimeStops[stop] {
			fmt.Printf("  通過%d: %s\n", i+1, arrivalTime)
		}
	}
}

出力結果

C:\Users\ebata>go run dummy1.go
A0(1): 06:00->A1(1): 06:05->A2(1): 06:10->A3(1): 06:15->A4(1): 06:20->A5(1): 06:25
A5(2): 06:35->A4(2): 06:40->A3(2): 06:45->A2(2): 06:50->A1(2): 06:55->A0(2): 07:00
A0(3): 07:10->A1(3): 07:15->A2(3): 07:20->A3(3): 07:25->A4(3): 07:30->A5(3): 07:35
routeOrder:
A0:
通過1: 06:00
通過2: 07:00
通過3: 07:10
A1:
通過1: 06:05
通過2: 06:55
通過3: 07:15
A2:
通過1: 06:10
通過2: 06:50
通過3: 07:20
A3:
通過1: 06:15
通過2: 06:45
通過3: 07:25
A4:
通過1: 06:20
通過2: 06:40
通過3: 07:30
A5:
通過1: 06:25
通過2: 06:35
通過3: 07:35

2024,江端さんの技術メモ

このプログラムの目的は、時刻表の乗り換え案内のアルゴリズムを実現する為のテストプログラムです。
「到着時刻より早い時間の電車やバスには乗れない」をダイクストラに組み込むことができるかを調べたものです。

ノードのValueが到着・出発時間を表わすと考えて下さい。

package main

import (
	"fmt"
	"math"
)

type Node struct {
	Name  string
	Value float64 // 各ノードに設定された数値
}

type Edge struct {
	From   *Node
	To     *Node
	Weight float64
}

func main() {
	/*
		// ノードとエッジを初期化
		nodeA := &Node{Name: "A", Value: 5}
		nodeB := &Node{Name: "B", Value: 8}
		nodeC := &Node{Name: "C", Value: 6}
		nodeD := &Node{Name: "D", Value: 2}
		nodeE := &Node{Name: "E", Value: 4}
	*/

	// ノードとエッジを初期化
	nodeA := &Node{Name: "A", Value: 1}
	nodeB := &Node{Name: "B", Value: 1}
	nodeC := &Node{Name: "C", Value: 0}
	nodeD := &Node{Name: "D", Value: 1}
	nodeE := &Node{Name: "E", Value: 1}

	/*
		edges := []Edge{
			{nodeA, nodeB, 2},
			{nodeA, nodeC, 4},
			{nodeB, nodeC, 1},
			{nodeB, nodeD, 7},
			{nodeC, nodeD, 3},
			{nodeC, nodeE, 5},
			{nodeE, nodeD, 2},
		}
	*/

	edges := []Edge{ // "方向性あり"に注意
		{nodeA, nodeB, 1},
		{nodeA, nodeC, 1},
		{nodeB, nodeC, 1},
		{nodeB, nodeD, 1},
		{nodeC, nodeD, 1},
		{nodeC, nodeE, 1},
		{nodeE, nodeD, 1},
		{nodeD, nodeE, 1},
	}

	startNode := nodeA
	targetNode := nodeE

	// ダイクストラアルゴリズムを実行
	shortestPath, totalWeight := dijkstra(startNode, targetNode, edges)

	if shortestPath == nil {
		fmt.Println("最短経路が見つかりませんでした。")
	} else {
		fmt.Printf("最短経路: %v\n", getNodeNames(shortestPath))
		fmt.Printf("最短経路の総重み: %.2f\n", totalWeight)
	}
}

func dijkstra(startNode, targetNode *Node, edges []Edge) ([]*Node, float64) {
	// ノード間の最短距離を格納するマップを初期化
	shortestDistances := make(map[*Node]float64)
	// 各ノードの前のノードを格納するマップを初期化
	predecessors := make(map[*Node]*Node)

	// 最短距離を無限大で初期化し、開始ノードの最短距離を0に設定
	for _, edge := range edges {
		shortestDistances[edge.From] = math.Inf(1)
		shortestDistances[edge.To] = math.Inf(1)
	}
	shortestDistances[startNode] = 0

	// 訪問済みのノードを格納するセットを初期化
	visitedNodes := make(map[*Node]bool)

	// まだ訪問していないノードが残っている間ループ
	for len(visitedNodes) < len(shortestDistances) {
		// 未訪問のノードの中から最短距離のノードを選択
		currentNode := getClosestUnvisitedNode(shortestDistances, visitedNodes)

		// ノードがない場合やターゲットノードに到達した場合は終了
		if currentNode == nil || currentNode == targetNode {
			break
		}

		// 隣接ノードの最短距離を更新
		for _, edge := range edges {
			//
			if edge.From == currentNode && edge.To.Value >= currentNode.Value { // ここがポイント
				distance := shortestDistances[currentNode] + edge.Weight
				if distance < shortestDistances[edge.To] {
					shortestDistances[edge.To] = distance
					predecessors[edge.To] = currentNode
				}
			}
		}

		// このノードを訪問済みとしてマーク
		visitedNodes[currentNode] = true
	}

	// 最短経路を復元
	shortestPath := make([]*Node, 0)
	currentNode := targetNode
	for currentNode != nil {
		shortestPath = append([]*Node{currentNode}, shortestPath...)
		currentNode = predecessors[currentNode]
	}

	// 最短経路の総重みを計算
	totalWeight := shortestDistances[targetNode]

	return shortestPath, totalWeight
}

func getClosestUnvisitedNode(distances map[*Node]float64, visitedNodes map[*Node]bool) *Node {
	minDistance := math.Inf(1)
	var closestNode *Node

	for node, distance := range distances {
		if !visitedNodes[node] && distance < minDistance {
			minDistance = distance
			closestNode = node
		}
	}

	return closestNode
}

func getNodeNames(nodes []*Node) []string {
	names := make([]string, len(nodes))
	for i, node := range nodes {
		names[i] = node.Name
	}
	return names
}

2023,江端さんの技術メモ

以下のtest.csvファイルを、gunplotで3D表示させたくて、色々やっています。

test.csv
#lat,lng,datetime,distance,speed
35.681100,139.758600,2023-05-22 02:10:30,313.307785,37.596934
35.683300,139.759900,2023-05-22 02:11:00,271.347933,32.561752
35.685300,139.760900,2023-05-22 02:11:30,240.030143,28.803617
35.685400,139.761000,2023-05-22 02:12:00,14.325264,1.719032
35.685400,139.761000,2023-05-22 02:12:30,0.000000,0.000000
35.685400,139.761100,2023-05-22 02:13:00,9.031610,1.083793
35.685200,139.763500,2023-05-22 02:13:30,217.896760,26.147611
35.684700,139.765700,2023-05-22 02:14:00,206.328362,24.759403
35.684200,139.768000,2023-05-22 02:14:30,215.040983,25.804918
35.685400,139.768400,2023-05-22 02:15:00,138.238013,16.588562

で、まあ、こんなgpファイルを使って試していましたが、上手く動きませんでした。

# Function to parse datetime string into a numerical value
strptime_datetime(x) = strptime('%Y-%m-%d %H:%M:%S', x)

# Set the output terminal to a 3D plot (you can change the output format if needed)
set terminal pngcairo enhanced size 800,600

# Set the data file separator to a comma
set datafile separator ','

# Set the axis labels
set xlabel 'Longitude (lng)'
set ylabel 'Latitude (lat)'
set zlabel 'Date and Time (datetime)'

# Set the view to a 3D perspective
set view 50,30,1,1


# Get the minimum and maximum datetime values from the data
stats 'test.csv' using (strptime_datetime(stringcolumn(3))) nooutput
min_datetime = STATS_min
max_datetime = STATS_max

# Set the range for the Z axis (datetime)
set zrange [min_datetime:max_datetime]

# Plot the data using the specified columns
#splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3))) with points pointtype 7 pointsize 1 title 'Data Points'
#splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3))) with points pointtype 7 pointsize 1 title 'Data Points'
#splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3))):(0) with points pointtype 7 pointsize 1 title 'Data Points'

splot 'test.csv' using 2:1:(timecolumn(stringcolumn(3)))

時間のパース(strptime_datetime(x) = strptime('%Y-%m-%d %H:%M:%S', x))がやっぱり上手く動きませんでした。

でまあ、しょうがないので、"2023-05-22 02:10:30"を秒数に変換するプログラムを作成しました。

/* "2023-05-22 02:10:30"を秒数に変換するプログラムを作成しました
   c:\Users\ebata\gnuplot\convert_datetime.go
*/
package main

import (
	"encoding/csv"
	"fmt"
	"os"
	"time"
)

func main() {
	// 入力ファイル名と出力ファイル名を指定
	inputFileName := "test.csv"
	outputFileName := "formatted_test.csv"

	// 入力ファイルを開く
	inputFile, err := os.Open(inputFileName)
	if err != nil {
		fmt.Println("ファイルを開けませんでした:", err)
		return
	}
	defer inputFile.Close()

	// 出力ファイルを作成または上書き
	outputFile, err := os.Create(outputFileName)
	if err != nil {
		fmt.Println("ファイルを作成できませんでした:", err)
		return
	}
	defer outputFile.Close()

	// CSVリーダーとライターを作成
	reader := csv.NewReader(inputFile)
	writer := csv.NewWriter(outputFile)

	// ヘッダーを読み込み、書き込み
	header, err := reader.Read()
	if err != nil {
		fmt.Println("ヘッダーを読み込めませんでした:", err)
		return
	}
	writer.Write(header)

	// データを読み込んで秒数に変換して書き込み
	for {
		record, err := reader.Read()
		if err != nil {
			break
		}

		// datetime列をパースして秒数に変換
		datetime := record[2] // datetime列のインデックスを確認してください
		parsedTime, err := time.Parse("2006-01-02 15:04:05", datetime)
		if err != nil {
			fmt.Println("日時をパースできませんでした:", err)
			return
		}
		seconds := parsedTime.Unix()

		// 秒数に変換した値を新しい列として書き込み
		record = append(record, fmt.Sprintf("%d", seconds))
		writer.Write(record)
	}

	// ライターをフラッシュしてクローズ
	writer.Flush()

	if err := writer.Error(); err != nil {
		fmt.Println("書き込みエラー:", err)
		return
	}

	fmt.Println("変換が完了しました。出力ファイル:", outputFileName)
}

で、こんなのができました。
formatted_test.csv

#lat,lng,datetime,distance,speed
35.681100,139.758600,2023-05-22 02:10:30,313.307785,37.596934,1684721430
35.683300,139.759900,2023-05-22 02:11:00,271.347933,32.561752,1684721460
35.685300,139.760900,2023-05-22 02:11:30,240.030143,28.803617,1684721490
35.685400,139.761000,2023-05-22 02:12:00,14.325264,1.719032,1684721520
35.685400,139.761000,2023-05-22 02:12:30,0.000000,0.000000,1684721550
35.685400,139.761100,2023-05-22 02:13:00,9.031610,1.083793,1684721580
35.685200,139.763500,2023-05-22 02:13:30,217.896760,26.147611,1684721610
35.684700,139.765700,2023-05-22 02:14:00,206.328362,24.759403,1684721640

スクリプトではなく、コマンドで一つづつ入れていきました。

gnuplot> set datafile separator ','   ← これ凄く重要
gnuplot> splot "formatted_test.csv" using 2:1:6
の結果は以下の通りでした。

エリア限定
# x軸の範囲を指定
set xrange [139.6119000:139.6312000]

# y軸の範囲を指定
set yrange [35.3627000:35.3737000] ←これだと、地図のイメージと逆転するので
set yrange [35.3737000:35.3627000]  ← y軸の範囲を逆転させる

上記のxrangeとyrangeの範囲を地図で示すと、

の範囲となる。
領域の範囲内でのトラッキングデータの様子

gnuplotでx軸とy軸の範囲を指定解除するには、以下のコマンドを使用する。

gnuplot> set xrange [*:*]
gnuplot> set yrange [*:*]

gnuplot> splot "2023-topLatLngCounts4-1.csv" using 2:1:3
gnuplot> set yrange [35.3737000:35.3627000]    ← 軸の大小を引っくり返す
gnuplot> splot "2023-topLatLngCounts4-1.csv" using 2:1:3 ← 点を表示
gnuplot> replot "2023-topLatLngCounts4-1.csv" using 2:1:3 with impulses ← 縦軸表示

ちなみに、文字列が入っているcsvだと表示されないことが多いようだ。

2023,江端さんの技術メモ

「交通流動量 パーソントリップ発生・集中量データ」のデータベースの作り方

サンプルファイル

F:\しゅらばしゅう\S05-a-10_SYUTO_GML

s05_xml2csv.zip (ダウンロードして解凍して下さい。sample1.xmlも入っています)

package main

import (
	"encoding/csv"
	"encoding/xml"
	"fmt"
	"os"
)

// XMLデータの構造体定義
type Dataset struct {
	Items []OccurredConcentratedTrafficVolumeOfPersonTrip `xml:"Occurred_ConcentratedTrafficVolumeOfPersonTrip"`
}

type OccurredConcentratedTrafficVolumeOfPersonTrip struct {
	ID                                       string `xml:"id,attr"`
	UrbanArea                                int    `xml:"urbanArea"`
	SurveyYear                               int    `xml:"surveyYear"`
	ConcentratedOccurrence                   int    `xml:"concentratedOccurrence"`
	ZoneCode                                 int    `xml:"zoneCode"`
	Railroad_NumberOfTripsForGoingToWork     int    `xml:"railroad_NumberOfTripsForGoingToWork"`
	Railroad_NumberOfTripsForGoingToSchool   int    `xml:"railroad_NumberOfTripsForGoingToSchool"`
	Railroad_NumberOfFreeTrips               int    `xml:"railroad_NumberOfFreeTrips"`
	Railroad_NumberOfBusinessTrips           int    `xml:"railroad_NumberOfBusinessTrips"`
	Railroad_NumberOfTripsForGoingHome       int    `xml:"railroad_NumberOfTripsForGoingHome"`
	Railroad_TotalNumberOfTrips              int    `xml:"railroad_TotalNumberOfTrips"`
	Bus_NumberOfTripsForGoingToWork          int    `xml:"bus_NumberOfTripsForGoingToWork"`
	Bus_NumberOfTripsForGoingToSchool        int    `xml:"bus_NumberOfTripsForGoingToSchool"`
	Bus_NumberOfFreeTrips                    int    `xml:"bus_NumberOfFreeTrips"`
	Bus_NumberOfBusinessTrips                int    `xml:"bus_NumberOfBusinessTrips"`
	Bus_NumberOfTripsForGoingHome            int    `xml:"bus_NumberOfTripsForGoingHome"`
	Bus_TotalNumberOfTrips                   int    `xml:"bus_TotalNumberOfTrips"`
	Automobile_NumberOfTripsForGoingToWork   int    `xml:"automobile_NumberOfTripsForGoingToWork"`
	Automobile_NumberOfTripsForGoingToSchool int    `xml:"automobile_NumberOfTripsForGoingToSchool"`
	Automobile_NumberOfFreeTrips             int    `xml:"automobile_NumberOfFreeTrips"`
	Automobile_NumberOfBusinessTrips         int    `xml:"automobile_NumberOfBusinessTrips"`
	Automobile_NumberOfTripsForGoingHome     int    `xml:"automobile_NumberOfTripsForGoingHome"`
	Automobile_TotalNumberOfTrips            int    `xml:"automobile_TotalNumberOfTrips"`
	Motorcycle_NumberOfTripsForGoingToWork   int    `xml:"motorcycle_NumberOfTripsForGoingToWork"`
	Motorcycle_NumberOfTripsForGoingToSchool int    `xml:"motorcycle_NumberOfTripsForGoingToSchool"`
	Motorcycle_NumberOfFreeTrips             int    `xml:"motorcycle_NumberOfFreeTrips"`
	Motorcycle_NumberOfBusinessTrips         int    `xml:"motorcycle_NumberOfBusinessTrips"`
	Motorcycle_NumberOfTripsForGoingHome     int    `xml:"motorcycle_NumberOfTripsForGoingHome"`
	Motorcycle_TotalNumberOfTrips            int    `xml:"motorcycle_TotalNumberOfTrips"`
	Walk_NumberOfTripsForGoingToWork         int    `xml:"walk_NumberOfTripsForGoingToWork"`
	Walk_NumberOfTripsForGoingToSchool       int    `xml:"walk_NumberOfTripsForGoingToSchool"`
	Walk_NumberOfFreeTrips                   int    `xml:"walk_NumberOfFreeTrips"`
	Walk_NumberOfBusinessTrips               int    `xml:"walk_NumberOfBusinessTrips"`
	Walk_NumberOfTripsForGoingHome           int    `xml:"walk_NumberOfTripsForGoingHome"`
	Walk_TotalNumberOfTrips                  int    `xml:"walk_TotalNumberOfTrips"`
	TotalNumberOfTrips                       int    `xml:"totalNumberOfTrips"`
}

func main() {
	// XMLファイルを読み込む
	xmlData, err := os.Open("sample1.xml")
	if err != nil {
		fmt.Println("Error opening XML file:", err)
		return
	}
	defer xmlData.Close()

	// XMLデータをデコード
	var dataset Dataset
	decoder := xml.NewDecoder(xmlData)
	if err := decoder.Decode(&dataset); err != nil {
		fmt.Println("Error decoding XML:", err)
		return
	}

	// CSVファイルにデータを書き込み
	csvFile, err := os.Create("output.csv")
	if err != nil {
		fmt.Println("Error creating CSV file:", err)
		return
	}
	defer csvFile.Close()

	csvWriter := csv.NewWriter(csvFile)
	defer csvWriter.Flush()

	// CSVヘッダを書き込み
	headers := []string{
		"ID", "UrbanArea", "SurveyYear", "ConcentratedOccurrence", "ZoneCode",
		"Railroad_NumberOfTripsForGoingToWork", "Railroad_NumberOfTripsForGoingToSchool", "Railroad_NumberOfFreeTrips",
		"Railroad_NumberOfBusinessTrips", "Railroad_NumberOfTripsForGoingHome", "Railroad_TotalNumberOfTrips",
		"Bus_NumberOfTripsForGoingToWork", "Bus_NumberOfTripsForGoingToSchool", "Bus_NumberOfFreeTrips",
		"Bus_NumberOfBusinessTrips", "Bus_NumberOfTripsForGoingHome", "Bus_TotalNumberOfTrips",
		"Automobile_NumberOfTripsForGoingToWork", "Automobile_NumberOfTripsForGoingToSchool", "Automobile_NumberOfFreeTrips",
		"Automobile_NumberOfBusinessTrips", "Automobile_NumberOfTripsForGoingHome", "Automobile_TotalNumberOfTrips",
		"Motorcycle_NumberOfTripsForGoingToWork", "Motorcycle_NumberOfTripsForGoingToSchool", "Motorcycle_NumberOfFreeTrips",
		"Motorcycle_NumberOfBusinessTrips", "Motorcycle_NumberOfTripsForGoingHome", "Motorcycle_TotalNumberOfTrips",
		"Walk_NumberOfTripsForGoingToWork", "Walk_NumberOfTripsForGoingToSchool", "Walk_NumberOfFreeTrips",
		"Walk_NumberOfBusinessTrips", "Walk_NumberOfTripsForGoingHome", "Walk_TotalNumberOfTrips",
		"TotalNumberOfTrips",
	}

	if err := csvWriter.Write(headers); err != nil {
		fmt.Println("Error writing CSV headers:", err)
		return
	}

	// データをCSVに書き込み
	//for _, person := range root.Persons {
	for _, item := range dataset.Items {

		// fmt.Println("pass1")
		// fmt.Println(item)

		//record := []string{item.ID, fmt.Sprintf("%d", item.UrbanArea), fmt.Sprintf("%d", item.SurveyYear), fmt.Sprintf("%d", item.ConcentratedOccurrence), fmt.Sprintf("%d", item.ZoneCode)}

		record := []string{
			item.ID,
			fmt.Sprintf("%d", item.UrbanArea),
			fmt.Sprintf("%d", item.SurveyYear),
			fmt.Sprintf("%d", item.ConcentratedOccurrence),
			fmt.Sprintf("%d", item.ZoneCode),
			fmt.Sprintf("%d", item.Railroad_NumberOfTripsForGoingToWork),
			fmt.Sprintf("%d", item.Railroad_NumberOfTripsForGoingToSchool),
			fmt.Sprintf("%d", item.Railroad_NumberOfFreeTrips),
			fmt.Sprintf("%d", item.Railroad_NumberOfBusinessTrips),
			fmt.Sprintf("%d", item.Railroad_NumberOfTripsForGoingHome),
			fmt.Sprintf("%d", item.Railroad_TotalNumberOfTrips),
			fmt.Sprintf("%d", item.Bus_NumberOfTripsForGoingToWork),
			fmt.Sprintf("%d", item.Bus_NumberOfTripsForGoingToSchool),
			fmt.Sprintf("%d", item.Bus_NumberOfFreeTrips),
			fmt.Sprintf("%d", item.Bus_NumberOfBusinessTrips),
			fmt.Sprintf("%d", item.Bus_NumberOfTripsForGoingHome),
			fmt.Sprintf("%d", item.Bus_TotalNumberOfTrips),
			fmt.Sprintf("%d", item.Automobile_NumberOfTripsForGoingToWork),
			fmt.Sprintf("%d", item.Automobile_NumberOfTripsForGoingToSchool),
			fmt.Sprintf("%d", item.Automobile_NumberOfFreeTrips),
			fmt.Sprintf("%d", item.Automobile_NumberOfBusinessTrips),
			fmt.Sprintf("%d", item.Automobile_NumberOfTripsForGoingHome),
			fmt.Sprintf("%d", item.Automobile_TotalNumberOfTrips),
			fmt.Sprintf("%d", item.Motorcycle_NumberOfTripsForGoingToWork),
			fmt.Sprintf("%d", item.Motorcycle_NumberOfTripsForGoingToSchool),
			fmt.Sprintf("%d", item.Motorcycle_NumberOfFreeTrips),
			fmt.Sprintf("%d", item.Motorcycle_NumberOfBusinessTrips),
			fmt.Sprintf("%d", item.Motorcycle_NumberOfTripsForGoingHome),
			fmt.Sprintf("%d", item.Motorcycle_TotalNumberOfTrips),
			fmt.Sprintf("%d", item.Walk_NumberOfTripsForGoingToWork),
			fmt.Sprintf("%d", item.Walk_NumberOfTripsForGoingToSchool),
			fmt.Sprintf("%d", item.Walk_NumberOfFreeTrips),
			fmt.Sprintf("%d", item.Walk_NumberOfBusinessTrips),
			fmt.Sprintf("%d", item.Walk_NumberOfTripsForGoingHome),
			fmt.Sprintf("%d", item.Walk_TotalNumberOfTrips),
			fmt.Sprintf("%d", item.TotalNumberOfTrips),
		}

		if err := csvWriter.Write(record); err != nil {
			fmt.Println("Error writing CSV record:", err)
			return
		}

	}

	fmt.Println("CSV file successfully created.")
}

 

2023,江端さんの技術メモ

マルチエージェントシミュレーションにおいて、エージェント間の相互作用を実現するためには、さまざまな方法が存在します。「模倣戦略」や「ペアワイズフェルミ法」以外の方法として以下のいくつかを考えることができます:

  1. ゲーム理論:
    • ナッシュ均衡やコアの概念を使用して、エージェント間の戦略的相互作用をモデル化する方法です。ゲーム理論を用いることで、競争や協力などの行動を表現できます。
  2. マーケットモデル:
    • エージェントがリソースや商品の交換を行う市場モデルを使用して、エージェント間の相互作用をシミュレートできます。価格設定や需要と供給の変動を考慮することができます。
  3. エージェントベースのモデリング:
    • エージェントが独自の行動ルールと目標を持つモデルを作成し、それらのエージェントが環境内で相互作用する方法です。エージェントの行動は、周囲のエージェントや環境に影響を与えます。
  4. 群集ダイナミクス:
    • エージェントが群れを形成し、群れ内での相互作用をモデル化する方法です。鳥の群れや魚の群れのような集団行動を研究するのに適しています。
  5. 進化アルゴリズム:
    • 遺伝的アルゴリズムや遺伝的プログラミングを使用して、エージェントの戦略や行動を進化させる方法です。最適な戦略を見つけるために遺伝子の操作を行います。

これらの方法は、異なる問題領域やシミュレーションの目的に応じて選択できます。具体的なシナリオや要件に合わせて、最適な方法を選んでください。

2023,江端さんの技術メモ

データベース(DB)のデータのインポートを開始して、すでに3時間が経過しようとしています。このインポートが、これまで作ってきたDBのテーブル全部を吹き飛ばすことがなかろうかと、とても心配しています。

User

dockerを使ってPostgresqlでDBを作っています。特にパラメタのセットとかはしていません。このような場合、DB容量の上限値をざっくりとで結構ですので、教えて下さい。

ChatGPT
Dockerコンテナ内でPostgreSQLをデフォルトの設定で実行する場合、データベースのサイズに特別な制限は設定されません。制限はDockerコンテナ自体のディスク容量に依存し、PostgreSQL自体は通常制限を設けません。

Dockerコンテナのディスク容量は、ホストマシンのディスク容量に依存し、Dockerコンテナが実行されているディスク領域によって決まります。したがって、デフォルトの設定であれば、PostgreSQLデータベースの容量制限はホストマシンのディスク容量に影響されます。

したがって、特定の容量制限を設定していない場合、PostgreSQLデータベースの上限容量は、ホストマシンのディスク容量に依存し、通常は制約を受けません。ホストマシンのディスク容量に制約がある場合、それが制限となります。

PostgreSQL自体にはデータベースサイズの制約を設定する設定がありますが、通常はデフォルトの設定で制限を設けることはありません。したがって、Dockerコンテナ内で特別な設定を行っていない場合、PostgreSQLデータベースのサイズは通常はホストマシンのディスク容量によって制約されます。


このような回答を得て、先ずは安堵していますが、それでもやっぱり怖いです。
Dockerコンテナの丸ごとバックアップを試みるべきでしょうか。
以前やったときは、データ本体がスッポリ抜けていたような、嫌な記憶があります。

 

 

2023,江端さんの技術メモ

Q: 緯度、軽度の点からなる鉄道路線のCSVデータがあるのですが、これが線の順番に並んでおらず、バラバラの順番になっています。このcsvファイルを線の順番に並び替えるGo言語プログラムを作成して下さい。

点画にすれば、こんな感じ

でも、線画にすると、こんな感じになる

で、このcsvのファイル名を、"your_csv_file.csv"として、以下のプログラムに放り込む

package main

import (
	"bufio"
	"encoding/csv"
	"fmt"
	"io"
	"math"
	"os"
	"strconv"
)

// 座標を表す構造体
type Coordinate struct {
	Latitude  float64
	Longitude float64
}

func main() {
	// CSVファイルのパス
	filePath := "your_csv_file.csv"

	// CSVファイルを読み込む
	coordinates, err := readCoordinatesFromFile(filePath)
	if err != nil {
		fmt.Println("Error reading CSV file:", err)
		return
	}

	// 座標を線の順番に並び替え
	sortedCoordinates := sortCoordinates(coordinates)

	// 結果を表示
	for _, coord := range sortedCoordinates {
		fmt.Printf("%f, %f\n", coord.Latitude, coord.Longitude)
	}
}

// CSVファイルから座標を読み込む関数
func readCoordinatesFromFile(filePath string) ([]Coordinate, error) {
	var coordinates []Coordinate

	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(bufio.NewReader(file))
	for {
		line, err := reader.Read()
		if err == io.EOF {
			break
		} else if err != nil {
			return nil, err
		}

		// CSVの各行から緯度と経度を抽出
		latitude, err := strconv.ParseFloat(line[0], 64)
		if err != nil {
			return nil, err
		}
		longitude, err := strconv.ParseFloat(line[1], 64)
		if err != nil {
			return nil, err
		}

		coordinates = append(coordinates, Coordinate{Latitude: latitude, Longitude: longitude})
	}

	return coordinates, nil
}

// 座標を線の順番に並び替える関数
func sortCoordinates(coordinates []Coordinate) []Coordinate {
	// 最初の座標をスタート地点として選択
	startIndex := 0
	sortedCoordinates := []Coordinate{coordinates[startIndex]}
	coordinates = append(coordinates[:startIndex], coordinates[startIndex+1:]...)

	// 座標を距離に基づいてソート
	for len(coordinates) > 0 {
		minIndex := findNearestCoordinateIndex(coordinates, sortedCoordinates[len(sortedCoordinates)-1])
		sortedCoordinates = append(sortedCoordinates, coordinates[minIndex])
		coordinates = append(coordinates[:minIndex], coordinates[minIndex+1:]...)
	}

	return sortedCoordinates
}

// 最も距離の近い座標のインデックスを検索する関数
func findNearestCoordinateIndex(coordinates []Coordinate, reference Coordinate) int {
	minDistance := math.MaxFloat64
	minIndex := 0

	for i, coord := range coordinates {
		distance := calculateDistance(coord, reference)
		if distance < minDistance {
			minDistance = distance
			minIndex = i
		}
	}

	return minIndex
}

// Haversine式を使用して座標間の距離を計算する関数
func calculateDistance(coord1, coord2 Coordinate) float64 {
	earthRadius := 6371.0 // 地球の半径(キロメートル)

	// 度数法からラジアンに変換
	lat1 := degToRad(coord1.Latitude)
	lon1 := degToRad(coord1.Longitude)
	lat2 := degToRad(coord2.Latitude)
	lon2 := degToRad(coord2.Longitude)

	// Haversine式による距離計算
	dlon := lon2 - lon1
	dlat := lat2 - lat1
	a := math.Pow(math.Sin(dlat/2), 2) + math.Cos(lat1)*math.Cos(lat2)*math.Pow(math.Sin(dlon/2), 2)
	c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
	distance := earthRadius * c

	return distance
}

// 度数法をラジアンに変換する関数
func degToRad(deg float64) float64 {
	return deg * (math.Pi / 180)
}

でてきたcsvをファイルにして表示すると、こんな感じになった。

だいぶ良くなったんだけど、変な直線が一本引かれている。

考察した結果、このプログラムは、
(Step.1) 出発点に一番近いノードを探す。
(Step.2)そのノードを次の出発点にして(Step.1)を行う
という処理をしているため、使われていないノードが、候補として残ってしまうという問題が生じる。

で、終端に至ったノードは使われていないノードの中から、一番近いノードを探してくるため、このような問題が発生してしまう、ということだろう。

対策は結構簡単で、ノード距離が異様に大きい場合は、そこで打ち切る、という処理を入れれば足る
(面倒なので、今回は、手動で削除した)

以上