/*
C:\Users\ebata\yamaguchi\src_light
■このサンプルプログラムのキモ
(1)座標を入力して一番近いpostGISのノードIDを検知して、ダイクストラ計算を行うこと
(2)ユーザ心理を計算するファジィ推論を行うこと
が入っているプログラムである。
■自動計算を実施するバッチ、dummy.batの内容は以下の通り
go run light-sim.go data/mod_20220522holyday_visitor.csv data/new_20220522holyday_visitor.csv
go run light-sim.go data/mod_20220521holyday_visitor.csv data/new_20220521holyday_visitor.csv
go run light-sim.go data/mod_20220522holyday.csv data/new_20220522holyday.csv
go run light-sim.go data/mod_20220518weekday.csv data/new_20220518weekday.csv
■その入力用のcsvファイルの一つである、"data/mod_20220522holyday_visitor.csv"の内容は以下の通り
34.172891,131.456346,34.182418,131.474987,21
34.163801,131.444142,34.164454,131.449142,62
34.158856,131.435881,34.164727,131.431189,52
(中略)
34.146351,131.461154,34.167045,131.448468,20
34.145639,131.449237,34.149603,131.432828,29
■"data/bike_stations.csv"の中身
index,station,address,lat,lon,initial_stock,max_stock
1,null,null,34.102543,131.392639,20,20
2,null,null,34.102543,131.392639,20,20
3,null,null,34.102543,131.392639,20,20
4,山口県庁前バス停,山口市春日町2086-4,34.183621,131.471688,20,20
5,null,null,34.102543,131.392639,20,20
6,山口市役所 駐輪場,山口市亀山町2-1,34.178056,131.474204,20,20
7,一の坂川交通交流広場,山口市中河原7-1,34.17960577,131.4783006,20,20
8,null,null,34.102543,131.392639,20,20
9,コープやまぐちこことどうもん店 駐輪場,山口市道場門前1-1-18,34.174234,131.474885,20,20
10,山口駅 駐輪場,山口市惣太夫町288-9,34.172219,131.480013,20,20
11,山口市教育委員会 駐輪場,山口市中央5-14-22,34.170346,131.46915,20,20
12,null,null,34.102543,131.392639,20,20
13,ファミリーマート山口泉都町店,山口市泉都町9-2,34.167346,131.462048,20,20
14,防長苑,山口市熊野町4-29,34.167204,131.45973,20,20
15,null,null,34.102543,131.392639,20,20
16,ホテルニュータナカ,山口市湯田温泉2-6-24,34.163937,131.456115,20,20
17,null,null,34.102543,131.392639,20,20
18,湯田温泉駅 駐輪場,山口市今井町146-6,34.159954,131.459967,20,20
19,アルク平川店,山口市平井724-1,34.152742,131.464199,20,20
20,山口大学(正門),山口市吉田1677-1,34.149852,131.466214,20,20
21,小郡総合支所 駐輪場,山口市小郡下郷609番地1,34.102543,131.392639,20,20
22,KDDI維新ホール 駐輪場,山口市小郡令和1丁目1番地,34.09368,131.394,20,20
23,風の並木通り(新山口駅南口側),山口市小郡金町1-1付近,34.092322,131.397667,20,20
24,平成公園 駐車場内,山口市小郡平成町3-1,34.088168,131.401698,20,20
25,null,null,34.102543,131.392639,20,20
26,アルク小郡店,山口市小郡下郷2273番地1,34.097135,131.391295,20,20
27,null,null,34.102543,131.392639,20,20
*/
package main
import (
"database/sql"
"encoding/csv"
"fmt"
"log"
"os"
"strconv"
_ "github.com/lib/pq"
)
// 自転車の型
type BikeParam struct {
id int // 自転車の識別番号
destinationId int // 出発座標番号(*1)
arrivalId int // 到着座標番号
// (*1)
// 名前がdestination(目的地)となっているのは、バスがユーザを迎えに行き、載せた時点が「出発」扱いであった名残。
// 自転車の場合は迎えに行く動作が無いので、名称変更が望ましい。
}
// 座標情報
type LocInfo struct {
Lng float64 // 経度
Lat float64 // 緯度
Source int // 地図DBのID
}
// 自転車の拠点
type BikeStation struct {
location LocInfo // 拠点の位置
initialStock int // 最初に配置する自転車の台数
stationName string // 拠点の名称
}
// ステーションからの自転車の出入り
type StationStock struct {
outgoing int
incoming int
}
var stationstock [40]StationStock
const STATIONS_PATH string = "data/bike_stations.csv"
// 拠点情報を読み込み、BikeStation型の配列を返す
func getStationInfo(dbMap *sql.DB) []BikeStation {
// ファイルをオープン
csvFile, err := os.Open(STATIONS_PATH)
if err != nil {
log.Fatal(err)
}
defer csvFile.Close()
// CSVファイルの中身を読み込み
r := csv.NewReader(csvFile)
rows, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
stationInfo := []BikeStation{} // 出力変数
// 行ごとに
for i, row := range rows {
if i == 0 {
continue // CSVのヘッダー行を無視
}
lat, err := strconv.ParseFloat(row[3], 64)
if err != nil {
log.Fatal(err)
}
lng, err := strconv.ParseFloat(row[4], 64)
if err != nil {
log.Fatal(err)
}
initialStockVal, err := strconv.Atoi(row[5])
if err != nil {
log.Fatal(err)
}
stationNameVal := row[1]
// CSV読み込み時点の座標のログ(地図DBによって補正する前の座標)
//log.Println("csv read result:", lng, lat, initialStock)
// 地図DBを参照することによって、ステーションの位置をノードの位置にする
source, lngModified, latModified := fixPosition(dbMap, lng, lat)
loc := LocInfo{
Lng: lngModified,
Lat: latModified,
Source: source,
}
// 読み込めていることの確認ログ
//log.Println("Station", (i - 1), lngModified, latModified, initialStock, source)
bikeStation := BikeStation{
location: loc,
initialStock: initialStockVal,
stationName: stationNameVal,
}
stationInfo = append(stationInfo, bikeStation)
}
return stationInfo
}
// Scan用の仮変数
var source int
var longitude float64
var latitude float64
var dist float64
// 指定した座標に近いDB上の座標を取得
func fixPosition(db *sql.DB, _x1, _y1 float64) (int, float64, float64) {
upperLimitMeter := 1500.0 // 近傍ノードの上限を1500 mに設定
str := fmt.Sprintf(
// 修正前: ways (道) の中から最近傍を取得
// "SELECT source, x1 AS longitude, y1 AS latitude, ST_Distance('SRID=4326;POINT(%v %v)'::GEOGRAPHY, the_geom) AS dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%v %v)'), %.1f) ORDER BY dist LIMIT 1",
// 修正後: ways_vertices_pgr (点座標) の中から最近傍を取得
"SELECT id AS source, lon AS longitude, lat AS latitude, ST_Distance('SRID=4326;POINT(%v %v)'::GEOGRAPHY, the_geom) AS dist FROM ways_vertices_pgr WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%v %v)'), %.1f) ORDER BY dist LIMIT 1",
_x1, _y1, _x1, _y1, upperLimitMeter,
)
//fmt.Println(str)
rows, err := db.Query(str)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
foundGoodMapNode := false
for rows.Next() {
foundGoodMapNode = true
if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
fmt.Println(err)
}
//fmt.Println(source, longitude, latitude, dist)
}
if !foundGoodMapNode {
log.Println("Warning: in func fixPosition: Good Map Node not found for query point (",
_x1, ",", _y1, ")")
}
return source, longitude, latitude
}
/*
func getShortestDistanceToBikeStation(dbMap *sql.DB, node int) int {
StationId := -1
distance := 1000000.0
for i := 0; i < 2; i++ {
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",
node,
Station[i].Node)
if errDijkstra != nil {
log.Fatal(errDijkstra)
}
defer rowsDijkstra.Close()
var agg_cost float64
for rowsDijkstra.Next() {
var x1, y1, x2, y2 float64
var seq, source, target int
err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost)
if err != nil {
fmt.Println(err)
}
}
if distance > agg_cost {
distance = agg_cost
StationId = i
}
}
return StationId
}
*/
// 江端修正版
func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal LocInfo) ([]LocInfo, float64) {
//log.Println("getDijkstraPath", locInfoStart, locInfoGoal)
var path []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",
locInfoStart.Source,
locInfoGoal.Source)
if errDijkstra != nil {
log.Fatal(errDijkstra)
}
defer rowsDijkstra.Close()
var agg_cost float64
isFirstCheck := true
isSourceCheck := true
for rowsDijkstra.Next() {
var x1, y1, x2, y2 float64
var seq int
var target int
// まずnodeを読む
if err := rowsDijkstra.Scan(&seq, &source, &target, &x1, &y1, &x2, &y2, &agg_cost); err != nil {
fmt.Println(err)
}
// 最初の1回だけ入る
if isFirstCheck {
if source == locInfoStart.Source {
isSourceCheck = true
} else {
isSourceCheck = false
}
isFirstCheck = false
}
var loc LocInfo
if isSourceCheck {
loc.Source = source
loc.Lng = x1
loc.Lat = y1
} else {
loc.Source = target
loc.Lng = x2
loc.Lat = y2
}
loc.Source = target
path = append(path, loc)
}
// ラストノードだけは手入力
path = append(path, locInfoGoal)
totalDistanceKm = agg_cost / 1000.0
return path, totalDistanceKm
}
// 一番近いステーションのIDを取得
func getNearestStation(dbMap *sql.DB, stationInfo []BikeStation, queryLocation LocInfo) (int, float64) {
bestDistanceKm := 1000.0 // 十分に大きい数
var bestStationId int
for i := 0; i < len(stationInfo); i++ {
// ダイクストラ法による経路で決定する距離
// SQLクエリを繰り返し実行するため、処理が遅くなる可能性がある
_, distKm := getDijkstraPath(dbMap, queryLocation, stationInfo[i].location)
// 直線距離の概算値(代替の計算方法)
//distKm, _ := distanceKm(queryLocation.Lng, queryLocation.Lat,
// stationInfo[i].location.Lng, stationInfo[i].location.Lat)
//log.Println("to station", i, "distanceKm=", distKm)
if distKm < bestDistanceKm {
bestDistanceKm = distKm
bestStationId = i
}
}
return bestStationId, bestDistanceKm
}
func main() {
dbMap, err := sql.Open("postgres",
"user=postgres password=password host=192.168.0.23 port=15432 dbname=yama_db sslmode=disable")
log.Println("------------------ map db open ------------------")
if err != nil {
log.Fatal("OpenError: ", err)
}
defer dbMap.Close()
// バイクステーション情報の読み込み
stationInfo := getStationInfo(dbMap)
//fmt.Println(stationInfo)
//file2, err2 := os.Open("data/new_20220518weekday.csv")
file2, err2 := os.Open(os.Args[1])
if err2 != nil {
log.Fatal(err2)
}
defer file2.Close()
r2 := csv.NewReader(file2)
rows2, err2 := r2.ReadAll() // csvを一度に全て読み込む
if err != nil {
log.Fatal(err2)
}
//file3, err3 := os.Create("data/calc2_new_20220518weekday.csv") // 第2パラメータ
file3, err3 := os.Create(os.Args[2]) // 第2パラメータ
if err3 != nil {
panic(err)
}
w := csv.NewWriter(file3)
output := []string{"id", "age", "origin_loc_Lng", "origin_loc_Lat", "dest_loc_Lng", "dest_loc_Lat", "distance_from_origin", "distance_between_stations", "distance_to_dest", "complain"}
if err = w.Write(output); err != nil {
log.Fatal(err)
}
for id, row := range rows2 {
/*
origin_lng := 131.4686813247102
origin_lat := 34.17901518198008
dest_lng := 131.45836175237153
dest_lat := 34.160484344205294
*/
origin_lng, err := strconv.ParseFloat(row[1], 64)
if err != nil {
log.Fatal(err)
}
origin_lat, err := strconv.ParseFloat(row[0], 64)
if err != nil {
log.Fatal(err)
}
dest_lng, err := strconv.ParseFloat(row[3], 64)
if err != nil {
log.Fatal(err)
}
dest_lat, err := strconv.ParseFloat(row[2], 64)
if err != nil {
log.Fatal(err)
}
age, err := strconv.ParseFloat(row[4], 64) // 年齢も実数扱いする
if err != nil {
log.Fatal(err)
}
// 最接近のステーションを選ぶ
var origin_loc, dest_loc LocInfo
origin_loc.Source, origin_loc.Lng, origin_loc.Lat = fixPosition(dbMap, origin_lng, origin_lat)
dest_loc.Source, dest_loc.Lng, dest_loc.Lat = fixPosition(dbMap, dest_lng, dest_lat)
stationId_from_origin, distance_from_origin := getNearestStation(dbMap, stationInfo, origin_loc) // Originから最初のステーション
stationId_to_dest, distance_to_dest := getNearestStation(dbMap, stationInfo, dest_loc) // 最後のステーションからDest
fmt.Println(stationInfo[stationId_from_origin])
stationstock[stationId_from_origin].outgoing++
fmt.Println(stationInfo[stationId_to_dest])
stationstock[stationId_to_dest].incoming++
_, distance_between_stations := getDijkstraPath(dbMap, stationInfo[stationId_from_origin].location, stationInfo[stationId_to_dest].location)
_, shortest_distance_total := getDijkstraPath(dbMap, origin_loc, dest_loc)
fmt.Println("stationId_from_origin, distance_from_origin", stationId_from_origin, distance_from_origin)
fmt.Println("stationId_to_dest, distance_to_dest", stationId_to_dest, distance_to_dest)
fmt.Println("distance_between_stations", distance_between_stations)
fmt.Println("shortest_distance_total", shortest_distance_total)
// 出発 ― _x[km]の歩行 ― 最初のステーション ― _y[km]の自転車走行 ― 最後のステーション ― _[km]の歩行 ― 到着
complain := fuzzy_reasoning(distance_from_origin, distance_between_stations, distance_to_dest, age)
fmt.Println("complain", complain)
output := []string{
fmt.Sprint(id),
fmt.Sprint(age),
fmt.Sprint(origin_loc.Lng),
fmt.Sprint(origin_loc.Lat),
fmt.Sprint(dest_loc.Lng),
fmt.Sprint(dest_loc.Lat),
fmt.Sprint(distance_from_origin),
fmt.Sprint(distance_between_stations),
fmt.Sprint(distance_to_dest),
fmt.Sprint(complain)}
fmt.Println(output)
if err = w.Write(output); err != nil {
log.Fatal(err)
}
}
// test
output = []string{"stationid", "outgoing", "incoming"}
if err = w.Write(output); err != nil {
log.Fatal(err)
}
for i := 0; i < 40; i++ {
output = []string{fmt.Sprint(i + 1), fmt.Sprint(stationstock[i].outgoing), fmt.Sprint(stationstock[i].incoming)}
// i+1しているのは、1からスタートする為
if err = w.Write(output); err != nil {
log.Fatal(err)
}
}
defer w.Flush()
if err := w.Error(); err != nil {
log.Fatal(err)
}
}
func fuzzy_reasoning(distance_from_origin, distance_between_stations, distance_to_dest, age float64) float64 {
////// パラメータの作成
// 絶対的歩行距離
walk := min_2(distance_from_origin, distance_to_dest)
// 総体的自転車移動距離
ratio := distance_between_stations / (distance_from_origin + distance_between_stations + distance_to_dest)
// 絶対的自転車移動距離
bike := distance_between_stations
// Age(前件部)
Age_Less := new_condition_MF3(45, 20, "LESS")
Age_Common := new_condition_MF3(45, 20, "COMMON") // 中央が 42歳
Age_More := new_condition_MF3(45, 20, "MORE")
// 絶対的歩行距離(前件部)
Walk_Less := new_condition_MF3(0.4, 0.1, "LESS")
Walk_Common := new_condition_MF3(0.4, 0.1, "COMMON")
Walk_More := new_condition_MF3(0.4, 0.1, "MORE")
// 相対的自転車移動比率(former)
Bike_Ratio_Less := new_condition_MF3(0.7, 0.1, "LESS")
Bike_Ratio_Common := new_condition_MF3(0.7, 0.1, "COMMON")
Bike_Ratio_More := new_condition_MF3(0.7, 0.1, "MORE")
// 絶対的自転車距離(前件部)
Bike_Less := new_condition_MF3(1.5, 1.0, "LESS")
Bike_Common := new_condition_MF3(1.5, 1.0, "COMMON")
Bike_More := new_condition_MF3(1.5, 1.0, "MORE")
// Complain(後件部)
Complain_LessLess := new_action_MF5(0.5, 0.25, "LESSLESS")
Complain_Less := new_action_MF5(0.5, 0.25, "LESS")
Complain_Common := new_action_MF5(0.5, 0.25, "COMMON")
Complain_More := new_action_MF5(0.5, 0.25, "MORE")
Complain_MoreMore := new_action_MF5(0.5, 0.25, "MOREMORE")
// [Rule A00]
Rule_A00 := min_2(Age_Less.func_X(age), Walk_Less.func_X(walk))
Complain_LessLess.func_Max(Rule_A00)
//fmt.Println("Rule_A00", Rule_A00)
// [Rule A01]
Rule_A01 := min_2(Age_Less.func_X(age), Walk_Common.func_X(walk))
Complain_Less.func_Max(Rule_A01)
//fmt.Println("Rule_A01", Rule_A01)
// [Rule A02]
Rule_A02 := min_2(Age_Less.func_X(age), Walk_More.func_X(walk))
Complain_Common.func_Max(Rule_A02)
//fmt.Println("Rule_A02", Rule_A02)
// [Rule A10]
Rule_A10 := min_2(Age_Common.func_X(age), Walk_Less.func_X(walk))
Complain_Common.func_Max(Rule_A10)
//fmt.Println("Rule_A10", Rule_A10)
// [Rule A11]
Rule_A11 := min_2(Age_Common.func_X(age), Walk_Common.func_X(walk))
Complain_Common.func_Max(Rule_A11)
//fmt.Println("Rule_A11", Rule_A11)
// [Rule A12]
Rule_A12 := min_2(Age_Common.func_X(age), Walk_More.func_X(walk))
Complain_More.func_Max(Rule_A12)
//fmt.Println("Rule_A12", Rule_A12)
// [Rule A20]
Rule_A20 := min_2(Age_More.func_X(age), Walk_Less.func_X(walk))
Complain_Common.func_Max(Rule_A20)
//fmt.Println("Rule_A20", Rule_A20)
// [Rule A21]
Rule_A21 := min_2(Age_More.func_X(age), Walk_Common.func_X(walk))
Complain_More.func_Max(Rule_A21)
//fmt.Println("Rule_A21", Rule_A21)
// [Rule A22]
Rule_A22 := min_2(Age_More.func_X(age), Walk_More.func_X(walk))
Complain_MoreMore.func_Max(Rule_A22)
//fmt.Println("Rule_A22", Rule_A22)
// [Rule B00]
Rule_B00 := Bike_Ratio_Less.func_X(ratio)
Complain_MoreMore.func_Max(Rule_B00)
//fmt.Println("Rule_B00", Rule_B00)
// [Rule B01]
Rule_B01 := Bike_Ratio_Common.func_X(ratio)
Complain_Common.func_Max(Rule_B01)
//fmt.Println("Rule_B01", Rule_B01)
// [Rule B02]
Rule_B02 := Bike_Ratio_More.func_X(ratio)
Complain_LessLess.func_Max(Rule_B02)
//fmt.Println("Rule_B02", Rule_B02)
// [Rule C00]
Rule_C00 := min_2(Age_Less.func_X(age), Bike_Less.func_X(bike))
Complain_LessLess.func_Max(Rule_C00)
//fmt.Println("Rule_C00", Rule_C00)
// [Rule C01]
Rule_C01 := min_2(Age_Less.func_X(age), Bike_Common.func_X(bike))
Complain_LessLess.func_Max(Rule_C01)
//fmt.Println("Rule_C01", Rule_C01)
// [Rule C02]
Rule_C02 := min_2(Age_Less.func_X(age), Bike_More.func_X(bike))
Complain_LessLess.func_Max(Rule_C02)
//fmt.Println("Rule_C02", Rule_C02)
// [Rule C10]
Rule_C10 := min_2(Age_Common.func_X(age), Bike_Less.func_X(bike))
Complain_Less.func_Max(Rule_C10)
//fmt.Println("Rule_C10", Rule_C10)
// [Rule C11]
Rule_C11 := min_2(Age_Common.func_X(age), Bike_Common.func_X(bike))
Complain_Common.func_Max(Rule_C11)
//fmt.Println("Rule_C11", Rule_C11)
// [Rule C12]
Rule_C12 := min_2(Age_Common.func_X(age), Bike_More.func_X(bike))
Complain_More.func_Max(Rule_C12)
//fmt.Println("Rule_C12", Rule_C12)
// [Rule C20]
Rule_C20 := min_2(Age_More.func_X(age), Bike_Less.func_X(bike))
Complain_Common.func_Max(Rule_C20)
//fmt.Println("Rule_C20", Rule_C20)
// [Rule C21]
Rule_C21 := min_2(Age_More.func_X(age), Bike_Common.func_X(bike))
Complain_More.func_Max(Rule_C21)
//fmt.Println("Rule_C21", Rule_C21)
// [Rule C22]
Rule_C22 := min_2(Age_More.func_X(age), Bike_More.func_X(bike))
Complain_MoreMore.func_Max(Rule_C22)
//fmt.Println("Rule_C22", Rule_C22)
// Reasoning calculations
numerator :=
Complain_LessLess.func_X()*Complain_LessLess.func_Y() +
Complain_Less.func_X()*Complain_Less.func_Y() +
Complain_Common.func_X()*Complain_Common.func_Y() +
Complain_More.func_X()*Complain_More.func_Y() +
Complain_MoreMore.func_X()*Complain_MoreMore.func_Y()
denominator :=
Complain_LessLess.func_Y() +
Complain_Less.func_Y() +
Complain_Common.func_Y() +
Complain_More.func_Y() +
Complain_MoreMore.func_Y()
reasoning := numerator / denominator
return reasoning
}
func max_2(a, b float64) float64 {
if a > b {
return a
} else {
return b
}
}
func min_2(a, b float64) float64 {
if a > b {
return b
} else {
return a
}
}
type condition_MF3 struct { // Base class for condition_MF3
center float64
width float64
express string
}
func new_condition_MF3(_center, _width float64, _express string) *condition_MF3 {
c3 := new(condition_MF3)
c3.center = _center
c3.width = _width
c3.express = _express
return c3
}
// Class for the membership function (3 mountains) of the former case
func (c3 *condition_MF3) func_X(_x float64) float64 {
// x,y denote coordinates on the membership function
x := _x
y := 0.0 // The value of y is always greater than or equal to 0 and less than or equal to 1
if c3.express == "LESS" {
if x <= c3.center-c3.width {
y = 1.0
} else if x <= c3.center {
y = -1.0 / c3.width * (x - c3.center)
} else {
y = 0.0
}
} else if c3.express == "COMMON" {
if x <= c3.center-c3.width {
y = 0.0
} else if x <= c3.center {
y = 1.0/c3.width*(x-c3.center) + 1.0
} else if x <= c3.center+c3.width {
y = -1.0/c3.width*(x-c3.center) + 1.0
} else {
y = 0.0
}
} else if c3.express == "MORE" {
if x <= c3.center {
y = 0.0
} else if x <= c3.center+c3.width {
y = 1.0 / c3.width * (x - c3.center)
} else {
y = 1.0
}
} else {
fmt.Println("MF3: wrong expression")
os.Exit(1)
}
return y
}
type condition_MF5 struct { // Base class for condition_MF5
center float64
width float64
express string
}
func new_condition_MF5(_center, _width float64, _express string) *condition_MF5 {
c5 := new(condition_MF5)
c5.center = _center
c5.width = _width
c5.express = _express
return c5
}
func (c5 *condition_MF5) func_X(_x float64) float64 {
// Class for the former membership function (5 mountains)
// x,y are the coordinates on the membership function
x := _x
y := 0.0 // The value of y is always greater than or equal to 0 and less than or equal to 1
if c5.express == "LESSLESS" {
if x <= c5.center-2.0*c5.width {
y = 1.0
} else if x <= c5.center-c5.width {
y = -1.0/c5.width*(x-(c5.center-2.0*c5.width)) + 1.0
} else {
y = 0.0
}
} else if c5.express == "LESS" {
if x <= c5.center-2.0*c5.width {
y = 0.0
} else if x <= c5.center-c5.width {
y = 1.0/c5.width*(x-(c5.center-c5.width)) + 1.0
} else if x <= c5.center {
y = -1.0/c5.width*(x-(c5.center-c5.width)) + 1.0
} else {
y = 0.0
}
} else if c5.express == "COMMON" {
if x <= c5.center-c5.width {
y = 0.0
} else if x <= c5.center {
y = 1.0/c5.width*(x-c5.center) + 1.0
} else if x <= c5.center+c5.width {
y = -1.0/c5.width*(x-c5.center) + 1.0
} else {
y = 0.0
}
} else if c5.express == "MORE" {
if x <= c5.center {
y = 0.0
} else if x <= c5.center+c5.width {
y = 1.0/c5.width*(x-(c5.center+c5.width)) + 1.0
} else if x <= c5.center+2.0*c5.width {
y = -1.0/c5.width*(x-(c5.center+c5.width)) + 1.0
} else {
y = 0.0
}
} else if c5.express == "MOREMORE" {
if x <= c5.center+c5.width {
y = 0.0
} else if x <= c5.center+2.0*c5.width {
y = 1.0/c5.width*(x-(c5.center+2.0*c5.width)) + 1.0
} else {
y = 1.0
}
} else {
fmt.Println("MF5 func_X(): wrong expression")
os.Exit(1)
}
return y
}
/////////////////////////////
type action_MF5 struct { // Base class for action_MF5
center float64
width float64
express string
x float64
y float64
}
type action_MF3 struct { // Base class for action_MF3
center float64
width float64
express string
x float64
y float64
}
func new_action_MF5(_center, _width float64, _express string) *action_MF5 {
a5 := new(action_MF5)
a5.center = _center
a5.width = _width
a5.express = _express
if a5.express == "LESSLESS" {
a5.x = a5.center - 2.0*a5.width
} else if a5.express == "LESS" {
a5.x = a5.center - a5.width
} else if a5.express == "COMMON" {
a5.x = a5.center
} else if a5.express == "MORE" {
a5.x = a5.center + a5.width
} else if a5.express == "MOREMORE" {
a5.x = a5.center + 2.0*a5.width
} else {
fmt.Println("new_action_MF5: wrong scale expression")
os.Exit(-1)
}
a5.y = 0.0
return a5
}
func new_action_MF3(_center, _width float64, _express string) *action_MF3 {
a3 := new(action_MF3)
a3.center = _center
a3.width = _width
a3.express = _express
if a3.express == "LESS" {
a3.x = a3.center - a3.width
} else if a3.express == "COMMON" {
a3.x = a3.center
} else if a3.express == "MORE" {
a3.x = a3.center + a3.width
} else {
fmt.Println("new_action_MF3: wrong scale expression")
os.Exit(-1)
}
a3.y = 0.0
return a3
}
// The latter membership function (5 mountains) class
func (a5 *action_MF5) func_Y() float64 {
return a5.y
}
// The latter membership function (3 mountains) class
func (a3 *action_MF3) func_Y() float64 {
return a3.y
}
func (a5 *action_MF5) func_Max(b float64) {
a5.y = max_2(b, a5.y)
}
func (a3 *action_MF3) func_Max(b float64) {
a3.y = max_2(b, a3.y)
}
func (a5 *action_MF5) func_X() float64 {
return a5.x
}
func (a3 *action_MF3) func_X() float64 {
return a3.x
}
座標を入力して一番近いpostGISのノードIDを検知して、ダイクストラ計算を行い、ユーザ心理を計算するファジィ推論を行うサンプルプログラム
「無償の愛」を『無期限』とする計画は、必ず破綻します ―― "必ず"です。
『6ヶ月』 ―― これが、私が計算と経験則から導き出した結論です。
Six months" is the conclusion I have drawn from my calculations and rule of thumb.
【新着記事】「江バ電」で人身事故をシミュレーションしてみた https://t.co/Qxr14X4COB pic.twitter.com/oJlAOsppaG
— EE Times Japan編集部 (@eetimes_jp) June 29, 2016
上記の私のコラムの5ページ目に、以下の記載があります。
You will find the following statement on page 5 of my column above.
■「この私」が、3・11の震災(東日本大震災)をどのように忘れていったのかを、定量的に知りたいと思いました。
- I wanted to know quantitatively how "this I" had forgotten about the 3/11 disaster (the Great East Japan)
■そこで、私がここ何年間、1日も欠かさずに記録し続けているブログを使って、以下のような調査をやってみました。
- I did the following survey using my blog, which I have kept track of without missing a single day for the past years.
-----
高齢者介護 ~医療の進歩の代償なのかhttps://t.co/R1DV5OshVJ pic.twitter.com/IiQbaWM0tR
— EE Times Japan編集部 (@eetimes_jp) July 30, 2018
上記の私のコラムの6ページ目に、以下の記載があります。
You will find the following statement on page 6 of my column above.
■ここから導かれる一つの仮説は、江戸時代以前の"寝たきり"とは、どんなに長くても半年程度であったということです。
- One hypothesis derived from this is that "bedridden" was only for about six months before the Edo period.
■当時の介護技術で、"寝たきり"を3年とか10年のオーダーで成立させるのは、無理だったはずです。
- With the care technology available at the time, it would have been impossible to keep a person "bedridden" for three or ten years.
-----
ウクライナ支援についても、各国の「支援疲れ」は、厳然たる事実です。
As for support for Ukraine, it is a stark fact that countries are "tired of supporting" Ukraine.
期限が定められていない支援に耐えられるほど、私たちは強くないのです。
We are not strong enough to withstand support without a set deadline.
-----
結論:
Conclusion:
(1)「他人への、条件のない愛情(無償の愛)のストックには上限がある」
(1) "There is an upper limit to the stock of unconditional love (free love) for others."
(2)「そのストックは、概ね6ヶ月で尽きる」
(2) "Its stock generally runs out in six months."
(3)「被災地支援、寝たきりの人への介護が「無償」で継続できる期間は、最長で"半年"である」
(3) "The maximum period during which support for disaster-affected areas and care for bedridden people can continue "free of charge" is "six months.
この現実をベースに、私たちは、被災者支援や高齢者介護を計画しなければならないと思います。
Based on this reality, we must plan to support the affected population and care for older people.
「無償の愛」を『無期限』とする計画は、必ず破綻します ―― "必ず"です。
The plan to make "free love" "indefinite" will surely fail -- "surely."
別のjsonのメッセージを送信して、それの受信が返ってこないと、APIが終了しません(デッドロックしてしまう)。この問題を解決するfastapiの簡単なプログラムを作成して下さい
■fastapi側プログラム dummy2.py
from fastapi import FastAPI
from pydantic import BaseModel
import asyncio # asyncio モジュールをインポート
app = FastAPI()
class InputMessage(BaseModel):
value: int
class OutputMessage(BaseModel):
response: int # 数値を含むレスポンスメッセージ
@app.post("/process")
async def process(input_message: InputMessage):
# 5秒間の待機
await asyncio.sleep(5)
# サーバー側で数値を受け取り、同じ数値を含むレスポンスメッセージを返す
response_value = input_message.value
output_message = OutputMessage(response=response_value)
return output_message
起動方法は、
uvicorn dummy2:app --host 0.0.0.0 --reload
■メッセージ送信側(クライアント)プログラム dummy3.py
import httpx
import sys
import asyncio # asyncio モジュールをインポート
async def send_message_to_api(value: int):
url = "http://localhost:8000/process" # FastAPIサーバーのエンドポイントURLを指定
input_message = {"value": value} # 送信する数値をJSONメッセージに含める
async with httpx.AsyncClient() as client:
response = await client.post(url, json=input_message, timeout=10.0)
if response.status_code == 200:
data = response.json()
print(f"APIからのレスポンス: {data['response']}")
else:
print(f"エラーが発生しました。ステータスコード: {response.status_code}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("使用法: python send.py <数値>")
sys.exit(1)
try:
value_to_send = int(sys.argv[1]) # コマンドライン引数から数値を取得
except ValueError:
print("数値を指定してください。")
sys.exit(1)
asyncio.run(send_message_to_api(value_to_send))
起動方法は、
>python dummy3.py 10
平行に3つ起動しても、正確に非同期処理してくれるようです。
昨夜から、デッドロック問題で、困っていたので、原点に戻って考え中です。
pythonからPIDをkillできない件
プロセスを強制終了しなければならないAPIを作っているのですが、「pythonからPIDをkillできない件」で困っていました。
で、以下の実験用プログラムを作成しました。
import sys
import os
import signal
def kill_process(pid):
try:
os.kill(pid, signal.SIGKILL) # 指定したプロセスIDをSIGKILLシグナルで終了
print(f"Process with PID {pid} has been killed.")
except OSError:
print(f"Failed to kill process with PID {pid}.")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python kill_process.py <PID>")
sys.exit(1)
try:
pid = int(sys.argv[1])
kill_process(pid)
except ValueError:
print("Invalid PID. Please enter a valid integer PID.")
で、
python3 kill_process.py 7870
を連発したのですが、全くプロセスが消えません(ps -ef | grep xxxxxなどでレビュー)
どうやら、最初に"sudo"を付けて、
sudo python3 kill_process.py 7870
で、起動してくれることが分かりました。
で、本家の問題ですが、fastapiを使ったプログラムを試してみたのですが、
sudo uvicorn test:app --host 0.0.0.0 --reload
では、エラーになります。これは環境変数を引きついでいない、とのことで、"-E"を付与することで、動くことを確認しました。
sudo -E uvicorn test:app --host 0.0.0.0 --reload
持っていかれた時間は4時間くらいかなぁ。
それでも、とりあえず動いて、次の開発に進めるので、安堵しています。
(こういう案件を夜に残すと、夜の眠りが浅くなる)。
コマンドを起動して、必要に応じて停止させ、プロセスID(PID)が消えるまでを監視する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
# 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)
コマンドを叩いてすぐにリターンするが、そのコマンドが終了するまで見張っているAPI
指定されたコマンド "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
時刻表ベースのダイクストラ計算方法
運行情報(ダイヤグラム)を反映した最短時間で到着する計算方法は、通常のダイクストラ法を拡張して実行することができます。以下は、ダイヤ情報を考慮する最短到着時間を計算する一般的なアプローチです:
- グラフの作成:
- ダイヤグラムに基づいて、駅間の接続を表すグラフを作成します。各エッジ(駅間の接続)には、所要時間が含まれます。
- グラフのノードは駅を表し、エッジの重みは駅間の移動にかかる時間です。
- 通常のダイクストラ法と同様に、出発駅を始点としてグラフを探索します。
- ノードの拡張と更新:
- 通常のダイクストラ法と同様に、出発駅から各駅への最短到着時間を記録するデータ構造を使用します。初期状態では、出発駅の最短到着時間を0に設定し、他の駅は無限大(無効値)とします。
- プライオリティキューを使用して、最短到着時間が最小の駅を選択します。
- ダイヤ情報の適用:
- 選択した駅から出発するエッジを調べ、ダイヤ情報を考慮して最短到着時間を計算します。
- ダイヤ情報には、駅への到着時間や運行間隔などが含まれます。現在の到着時間とダイヤ情報を使用して、次の駅への最短到着時間を計算します。
- 新しい到着時間が現在の最短到着時間よりも短い場合、その駅の最短到着時間を更新します。
- プライオリティキューから次の最短到着時間の駅を選択し、ステップ3を繰り返します。目的の駅に到達した場合、計算を終了します。
- 最短到着時間を使用して、目的の駅への最短経路を復元します。
このアプローチを使用すると、ダイヤ情報を反映した最短到着時間を計算できます。ダイヤ情報は、駅への到着時間や運行間隔を正確に取得し、計算に組み込む必要があります。また、プライオリティキューの実装や適切なデータ構造の設計も重要です。
簡易バスダイヤ作成プログラム
/*
簡易バスダイヤ作成プログラム 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
ノード間のコストが与えられているダイクストラ計算を行うノードのそれぞれに数値が設定されていて、ノードが持っている数値より小さい数値のノードとは繋がることができない、というアルゴリズムをGo言語で作成する
このプログラムの目的は、時刻表の乗り換え案内のアルゴリズムを実現する為のテストプログラムです。
「到着時刻より早い時間の電車やバスには乗れない」をダイクストラに組み込むことができるかを調べたものです。
ノードの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
}
トラッキングのcsvデータをgnuplotで表示してみる
以下の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だと表示されないことが多いようだ。