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

## モチベーション

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

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

## OSSルーティングエンジン

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

![image](https://qiita-image-store.s3.amazonaws.com/0/38598/5c66ae8c-a501-cfbc-4bd0-53fa7d324cb0.png)


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

- pgRouting
- GraphHopper
- Open Source Routing Machine
- Valhalla

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

## GraphHopper とは

![image](https://qiita-image-store.s3.amazonaws.com/0/38598/677d3e14-1b1e-5747-fcbf-d5d64aa319fa.png)
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

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

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

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

```bash
$ ./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アプリケーションで確認可能。

![image](https://qiita-image-store.s3.amazonaws.com/0/38598/b33c3df9-6d6f-b56e-3517-4379d373732c.png)


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

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

![image](https://qiita-image-store.s3.amazonaws.com/0/38598/97ed0660-6bee-4f9c-30f0-0ac385a7f5fc.png)


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

## Web API

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

![image](https://qiita-image-store.s3.amazonaws.com/0/38598/2e187f53-774d-03a3-c003-3ab180c2a5df.png)


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

```json
{
  "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 形式で吐き出すプログラムを書いてみた。

```ruby: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
```

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

```bash
$ ruby to_geojson.rb > route.geojson
```

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

![image](https://qiita-image-store.s3.amazonaws.com/0/38598/580eaf12-666f-3a89-41bf-a9e6f2c61701.png)



## GraphHopper 所感

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

## 今後の展開

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