pgr_dijkstra()で、ダイクストラの順番を壊さずにルートの座標を得る方法(getDijkstraPath())
以前、pgr_dijkstra()で、ダイクストラの順番が壊れる という内容で悩んでいて、最終的に、
utsu_tram_db3=# SELECT seq, source, edge, x1, y1 FROM pgr_dijkstra('SELECT gid as id, source, target, cost, reverse_cost FROM ways', 2, 59, directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq;
で、順列を壊さないで、ダイクストラの表示ができる、ということを書きました。
ところが、まだ、これでも問題が発生することが分かりました。
ノード1799からノード3342のルート計算を以下のようにやってみました。
kitaya_db=# SELECT seq, source, target, x1, y1,x2,y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target,
length_m as costcost, reverse_cost FROM ways', 3342, 1799, directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq;
まあ、こんな感じで、sourceとx1,x2を追っていって、ラストのtargetとx2,y2を拾えば、いいと分かりましたので、これで大丈夫だろう、と思ってコーディングしていました。
ところが、このノード1799からノード3342を逆転させて、ノード3342からノード1799のルート計算を以下のようにやってみました。
kitaya_db=# SELECT seq, source, target, x1, y1,x2,y2, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target,
length_m as costcost, reverse_cost FROM ways', 1799, 3342, directed:=false) a INNER JOIN ways b ON (a.edge = b.gid) ORDER BY seq;
と、こんな感じで、sourceが出発点にならずに、targetの方が正しい並びとなってしまっています。つまり、こんな感じ。
で、これがどっちで出てくるのか分からないので、以下のようにしました。
(1)最初のノードがsourceに出てきたら、sourceベースで読み出し、
(2)最初のノードがtargetに出てきたら、targetベースで読み出す
実装はこんな感じにしました。
type LocInfo struct {
Lon float64
Lat float64
Source int
}
// 江端修正版 func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal ldarp.LocInfo) ([]ldarp.LocInfo, float64) { log.Println("getDijkstraPath", locInfoStart, locInfoGoal) 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", 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 ldarp.LocInfo if isSourceCheck { loc.Source = source loc.Lon = x1 loc.Lat = y1 } else { loc.Source = target loc.Lon = x2 loc.Lat = y2 } loc.Source = target path = append(path, loc) } // ラストノードだけは手入力 path = append(path, locInfoGoal) totalDistanceKm = agg_cost / 1000.0 return path, totalDistanceKm }
もっとクールな方法があるかもしれませんが、面倒なので、戦うのはやめました。
バグを発見したので、main()を含めた再修正版をアップしておきまます。
package main
import (
"database/sql"
"fmt"
"log"
"m/src/ldarp"
_ "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()
var a_Point, b_Point ldarp.LocInfo
a_Point.Source = 20
b_Point.Source = 1
path, dist := getDijkstraPath(db, a_Point, b_Point)
fmt.Println("path:", path)
fmt.Println("dist:", dist)
}
// 江端再修正版
func getDijkstraPath(dbMap *sql.DB, locInfoStart, locInfoGoal ldarp.LocInfo) ([]ldarp.LocInfo, float64) {
log.Println("getDijkstraPath", locInfoStart, locInfoGoal)
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 cost, reverse_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
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)
}
// ラストノードだけは手入力 (ここは引っくり返す)
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
}