新・カーナビ作るぜ!(5)pgRoutingで経路探索
プラグインの話とかgeodata APIの話とか、いろいろ書かずに溜っていることがあるんですが、まぁ、blog調なんで、今やっていることを忘れないうちに書いていくことに。
カーナビというからにはナビゲーションが必要なわけで、そのためには移動経路が必要。経路探索はもともとの私の専門分野から遠いわけではないので、自分でアルゴリズムからなにから考えて実装してもいいんですが、postgisの理解とかいろいろしなきゃいけないことが多いので、今回は自実装は避けて既存の物を使うことに。で、前からOSMのMLなどで情報が流れていて気になっていたpgRoutingを使うことに。これは、postgreSQLのライブラリとして働いて、SQLで経路探索ができるというすぐれもの。
また、プラグインとしてtsmapに組み込むところまではいっていないんですが、昨日、一日いじってみて、うまくいきそうなので、とりあえず設定等をメモ書き。
当初、mapnikで使っているテーブルがそのまま使えるのかと勘違いして(←マニュアル読め!)適当にやってみたのだがうまくいかず。どうやら専用のDBが必要なよう。
どうも、日本語だと適切なドキュメントが見付からず、あきらめて、pgRouting のサイトの英語ドキュメントを読むことに。(インストールについては ここらへん に日本語があります)
あ、ちなみに、私は英語ドキュメントを読む際にはまじめには読みません。図表やコードの前後だけちらっと読むだけです。なので、深い理解は得られてませんので、間違いがあるかも知れず、悪しからずご了承ください。
日本語で読んでもわからなかったのは、OSMのデータを使う方法。 osm2pgrouting を使えばよいことはわかったのですが、その前に、 テーブルを作って 必要なfunctionの登録をしておく必要があるようです。
$ createdb -E UTF-8 routing $ createlang plpgsql routing $ psql -f /usr/share/pgsql/contrib/lwpostgis-64.sql routing $ psql -f /usr/share/pgsql/contrib/spatial_ref_sys.sql routing $ psql -f /usr/share/postlbs/routing_core.sql routing $ psql -f /usr/share/postlbs/routing_core_wrappers.sql routing $ psql -f /usr/share/postlbs/routing_topology.sql routingでもって、osm2pgroutingをsvnでとってきてビルドしてインストールしておいて、日本部分のデータ(は ここ からとってきてます)を入れる。
$ svn checkout http://pgrouting.postlbs.org/svn/pgrouting/tools/osm2pgrouting/trunk osm2pgrouting $ cd osm2pgrouting/ $ make $ bunzip2 japan.osm.bz2 $./osm2pgrouting -file japan.osm -conf mapconfig.xml -dbname routing -user postgres -host /tmp -clean
これで準備完了。
使いかたは、スタート地点と目的地点の近くにある座標からその付近にあるWayのノードを探してきて、スタート・ゴール地点としてあたえると間の点を列挙してくれます。
とりあえず、OSMからスタート・ゴール地点の座標を探します(右下のPermalinkのURLから緯度・経度を取得)。SELECT gid,source,target FROM ways ORDER BY Distance(the_geom,GeomFromText('POINT(139.36801 35.60284)',4326)) limit 3;
こんな感じです。POINTの中に入るのが緯度・経度。source/targetの違いが今ひとつわかっておらずとりあえず、sourceの値を使うことに。
で、得られた点の番号を元に、
SELECT * FROM shortest_path('SELECT gid as id, source::integer, target::integer, length::double precision as cost FROM ways', 29157, 23744, false, false);
こんな風にSQL文を各と経路が出てきます。20157がスタート地点で23744がゴール地点。経路を求めるアルゴリズムはダイクストラを使っています。ダイクストラ以外にもpgRoutingでは、A*(エースター)や右折禁止などを取り込んだ、ShootingStarなども使えるようです。
結果:
vertex_id | edge_id | cost
-----------+---------+---------------------
29157 | 45761 | 0.142505143803212
28101 | 45760 | 0.00976699131720564
29160 | 123504 | 0.148050831538797
29154 | 123503 | 0.126977657452652
75297 | 123502 | 0.0376059924170061
30940 | 123501 | 0.037602851233578
75296 | 123500 | 0.0993425476873406
75295 | 123498 | 0.052708204656941
23101 | 25901 | 0.125067304565714
23100 | 25900 | 0.293038867388181
23099 | 25899 | 0.136760788051705
23098 | 25898 | 0.421534996481485
23094 | 25897 | 0.153826012991872
23097 | 25896 | 0.487921749434503
23096 | 26834 | 0.251038430293651
23737 | 26835 | 0.0248279897353116
23738 | 26836 | 0.194730873904639
23739 | 26837 | 0.0101015081657756
21597 | 26838 | 0.149845218590008
23740 | 26839 | 0.079898495421177
23741 | 26840 | 0.0118690508992281
22877 | 26841 | 0.0941266246743623
22875 | 26842 | 0.356528456240591
23742 | 26843 | 0.164652655913974
23012 | 26844 | 0.374635198854382
23743 | 26845 | 0.473930741991173
23744 | -1 | 0
(27 rows)
さて、ここで得られるのは、頂点番号なので、これを座標に変換する必要があります。vertices_tmpというテーブルがあって番号を元にこっから取ってくればよいようなので、プログラムからSQL文でとも思ったのですが、繰り返しQueryを呼ぶのはどうも効率が悪い気がします。で、単純にjoinしてみたんですが、出力結果の順番が変わってしまい、うまくいきません。しょうがないので、出力に行番号を無理くりつけておいてあとでソートしなおすことに。
こんな感じです:create temp sequence counter;
select ctr,vertex_id,AsText(the_geom) as the_geom from (SELECT nextval('counter') as ctr,* FROM shortest_path('SELECT gid as id, source::integer, target::integer, length::double precision as cost FROM ways', 29157, 23744, false, false)) as rt join vertices_tmp on rt.vertex_id=vertices_tmp.id order by ctr;
結果:
ctr | vertex_id | the_geom -----+-----------+------------------------------- 1 | 29157 | POINT(139.3680508 35.6029347) 2 | 28101 | POINT(139.3664778 35.6030166) 3 | 29160 | POINT(139.3663705 35.6030268) 4 | 29154 | POINT(139.3647407 35.6031563) 5 | 75297 | POINT(139.3633468 35.6032962) 6 | 30940 | POINT(139.3629376 35.6033569) 7 | 75296 | POINT(139.3625299 35.6034238) 8 | 75295 | POINT(139.3614765 35.603678) 9 | 23101 | POINT(139.3609207 35.6038211) 10 | 23100 | POINT(139.3596307 35.6042273) 11 | 23099 | POINT(139.3566401 35.6052436) 12 | 23098 | POINT(139.3552397 35.6057087) 13 | 23094 | POINT(139.3508994 35.6070939) 14 | 23097 | POINT(139.349324 35.6076166) 15 | 23096 | POINT(139.3447382 35.6099305) 16 | 23737 | POINT(139.3436489 35.6120072) 17 | 23738 | POINT(139.3435412 35.6122126) 18 | 23739 | POINT(139.3426531 35.6138081) 19 | 21597 | POINT(139.342594 35.6138852) 20 | 23740 | POINT(139.3417493 35.6150447) 21 | 23741 | POINT(139.3413283 35.6156765) 22 | 22877 | POINT(139.3412735 35.6157735) 23 | 22875 | POINT(139.3409674 35.6165826) 24 | 23742 | POINT(139.3409194 35.6197887) 25 | 23012 | POINT(139.3410325 35.6212666) 26 | 23743 | POINT(139.3391497 35.6242681) 27 | 23744 | POINT(139.3369666 35.6281433) (27 rows)ついでなんで、通過道路名も出るとよいかなぁ、と思って書いたのがこちら:
select ctr,vertex_id,AsText(vertices_tmp.the_geom),ways.name as the_geom from (SELECT nextval('counter') as ctr,* FROM shortest_path('SELECT gid as id, source::integer, target::integer, length::double precision as cost FROM ways', 29157, 23744, false, false)) as rt join vertices_tmp on rt.vertex_id=vertices_tmp.id join ways on rt.edge_id=ways.gid order by ctr;
交差点名もとりたいなぁと思ったんですが…、ん?データとして入ってなさそうな。
もちっと調べてみよう。
今日はこんなところで。
Posted by TechStrom on Saturday, September 19, 2009