OSSルーティングエンジン「GraphHopper」と戯れてみた


モチベーション

位置情報ログの分析・解析用途でルーティングエンジンを活用してみたい!

  • ユーザーの経路検索ログを可視化
  • GPSログの経路マッチング
  • ネットワークベースの到達圏解析
  • etc...

OSSルーティングエンジン

いろいろ調べた結果、結局 OpenStreetMap Wiki にまとめられている情報にたどり着いた。
https://wiki.openstreetmap.org/wiki/Routing

気になったプロダクトは以下。

  • pgRouting
  • GraphHopper
  • Open Source Routing Machine
  • Valhalla

その中で、今回は GraphHopper と戯れてみたというお話。

GraphHopper とは


https://github.com/graphhopper/graphhopper

  • Java実装のOSSルーティングエンジン
  • OSMデータ(OpenStreetMapデータ)からネットワークデータを生成して動作する
    • 他データ形式もそれ用のインポートツールがあれば対応される
  • 動作要件は「JRE (Java 7+) がインストールされていること」のみ
    • ただしネットワークデータを展開するだけのメモリ領域は必要
    • また起動スクリプトのリモートOSMファイル取得で wget コマンドを利用
  • 「とりあえず動かしてみる」までの敷居が低かったので手始めに触ってみた

Getting Started

ドキュメントのインストラクションに従う。
https://github.com/graphhopper/graphhopper/blob/0.8/docs/core/quickstart-from-source.md

ソースコードの取得と、最新安定版へのチェックアウト。

$ git clone git://github.com/graphhopper/graphhopper.git
$ cd graphhopper; git checkout 0.8

プロジェクトルートにある graphhopper.sh でビルド、OSMデータの取得、OSMデータからネットワークデータの生成、アプリケーションの起動まで面倒みてくれる。

$ ./graphhopper.sh web europe_germany_berlin.pbf

OSMデータがローカルになければリモートから取得してくれる。
ダイアログが表示されるので、そのまま ENTER を押下でOSMデータがダウンロードされる。

## using java 1.8.0_111 (64bit) from 
File not found 'europe_germany_berlin.pbf'. Press ENTER to get it from: http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf
Press CTRL+C if you do not have enough disc space or you don't want to download several MB.

OSMデータのダウンロードが完了するとネットワークデータの生成が始まる。データ量によってそこそこ時間がかかる。
最後にWebアプリケーションの起動ログが出力されればOK(HTTP :8989と起動ポートが表示される)。

:
2016-11-02 19:08:43,186 [main] INFO  com.graphhopper.http.GHServer - Started server at HTTP :8989

http://localhost:8989/ にアクセスするとWebアプリケーションで確認可能。

日本のエリアでアプリケーションを動かすには、以下コマンドで。メモリを 2000m (デフォルトだと 1000m )で確保しないと OutOfMemory となったので JAVA_OPTS で指定している。

$ JAVA_OPTS="-Xmx2000m -Xms2000m -server" ./graphhopper.sh web asia_japan.pbf

意外とジオコーダーっぽいものも動いている

Web API

もちろん、 HTML ページではなく Web API のインターフェースも用意されている。ドキュメントは以下。
https://github.com/graphhopper/graphhopper/blob/0.8/docs/web/api-doc.md

「田町駅」から「駒場東大前」までのルートレスポンスは以下の通り。
http://localhost:8989/route?point=35.645736,139.747683&point=35.658902,139.683363&points_encoded=false&instructions=false&debug=true

{
  "hints": {
    "visited_nodes.average": "234.0",
    "visited_nodes.sum": "234"
  },
  "paths": [{
    "descend": 0,
    "ascend": 0,
    "distance": 9681.893,
    "bbox": [
      139.682723,
      35.645041,
      139.748104,
      35.665978
    ],
    "weight": 479.639483,
    "time": 479630,
    "points_encoded": false,
    "points": {
      "coordinates": [
        [
          139.747846,
          35.646186
        ],
        [
          139.748058,
          35.646301
        ],
        [
          139.748089,
          35.646341
        ],
        :
        (省略)
        :
        [
          139.683392,
          35.658996
        ]
      ],
      "type": "LineString"
    },
    "snapped_waypoints": {
      "coordinates": [
        [
          139.747846,
          35.646186
        ],
        [
          139.683392,
          35.658996
        ]
      ],
      "type": "LineString"
    }
  }],
  "info": {
    "took": 11,
    "copyrights": [
      "GraphHopper",
      "OpenStreetMap contributors"
    ]
  }
}

WebAPI経由で他プログラムから経路検索

Ruby で Web API を叩いて、結果を GeoJSON 形式で吐き出すプログラムを書いてみた。

to_geojson.rb
require 'net/http'
require 'json'

# INPUT
start_point = "35.645736,139.747683"
end_point = "35.658902,139.683363"

# リクエスト
uri = URI.parse('http://localhost:8989/route')
uri.query = URI.encode_www_form({
  point: [start_point, end_point],
  points_encoded: "false",
  instructions: "false",
  debug: "true",
})
res = Net::HTTP.get_response(uri).body

# GeoJSONに変換
res_json = JSON.parse(res)
coordinates = res_json["paths"][0]["points"]["coordinates"]
geojson = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      geometry: {
        type: "LineString",
        coordinates: coordinates,
      }, 
      properties: {
        "stroke": "#FF0000",
        "stroke-opacity": 0.8,
        "stroke-width": 5,
      },
    }
  ]
}

# 出力
puts JSON.pretty_generate geojson

以下な感じで実行して結果をファイル出力。

$ ruby to_geojson.rb > route.geojson

この出力した GeoJSON ファイルを GitHub にコミットすればプレビューされる。

GraphHopper 所感

  • OSMデータのサイズでダウンロードおよびネットワークデータの展開はそこそこ時間がかかる
  • データ処理時間を除けば「とりあえず触ってみる」の敷居は低いっぽい
  • デフォルトだと車の経路検索となる、歩行者の経路検索を行う設定カスタマイズを確認したい
  • 経路マッチングを行う map-matching なるプロダクトもあり、これも試したい
  • 位置情報ログの分析・解析用途に利用するにはこれでいい感じっぽい

今後の展開

  • GraphHopperの設定カスタマイズや map-matching などの機能を試して、分析・解析用途の幅を広げたい
  • 他のルーティングエンジンも評価して、それぞれの特徴や強みなどを整理したい