経路探索してみる話(その1)

[ヴァル研究所 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/val)、4日目はいよいよ[経路探索](http://docs.ekispert.com/v1/api/search/course/extreme.html)の話しをしてみようと思います。

[経路探索](http://docs.ekispert.com/v1/api/search/course/extreme.html)はドキュメントにもあるように、指定された駅もしくは地点間の公共交通機関を利用した経路を探索する機能です。
(「駅すぱあと」の主たる機能のひとつですね！)

## 経路探索の例

例として、新宿から高円寺までの経路を探索してみます。昨日の[駅コードの話し](http://qiita.com/valsitoh/items/ebd0d279a13fd21cbfec)にもある通り、最初に駅名を駅コードに変換しましょう。
新宿の駅コードは`22741`、高円寺の駅コードは`22671`であることが分かります。
(この場合は「新宿」が含まれる駅名が複数あるので注意ですね)

```shell-session
$ curl -s -XGET 'http://api.ekispert.jp/v1/json/station?key=<アクセスキー>&name=新宿&type=train' \
      | jq '.ResultSet.Point[].Station' \
      | egrep '(code|Name)' \
      | paste - - 
  "code": "22741",        "Name": "新宿",
  "code": "29342",        "Name": "新宿(東京メトロ)",
  "code": "22768",        "Name": "西武新宿",
  "code": "22742",        "Name": "新宿御苑前",
  "code": "22743",        "Name": "新宿三丁目",
  "code": "29330",        "Name": "新宿西口",
$ curl -s -XGET 'http://api.ekispert.jp/v1/json/station?key=<アクセスキー>&name=高円寺&type=train' \
      | jq '.ResultSet.Point.Station' \
      | egrep '(code|Name)' \
      | paste - - 
  "code": "22671",        "Name": "高円寺",
```

続いて取得した駅コードを用いて[経路探索](http://docs.ekispert.com/v1/api/search/course/extreme.html)を呼び出します。
必須パラメータはアクセスキー(`key`)と`viaList`になります。`viaList`パラメータに出発駅と到着駅をコロン(":")で区切って指定します。
また、デフォルトでは現在の日付、日時での探索となります。日時を指定する場合は、`date`と`time`パラメータを指定してください。

```shell-session
curl -s -XGET 'http://api.ekispert.jp/v1/json/search/course/extreme?key=<アクセスキー>&viaList=22741:22671'
```

探索結果は以下のようになります。`ResultSet/Course`要素に探索結果が含まれる形のレスポンスになっています。

```json
{
  "ResultSet": {
    "apiVersion": "1.27.0.0",
    "engineVersion": "201612_02a",
    "Course": [
      {
        "searchType": "departure",
        "dataType": "onTimetable",
        "SerializeData": "VkV4QaEByALDAqYBM6KyA6YBM6KyBJHCAUMAAAACpgEzorIDyQEBAgEDAQQBBwEIAQoCD0L_fxCl__cFlMECpVjVwQKlWI_BAqVZysECpVdZBpHDAQECAQPHAaUkJQIeAyIFpHkGRTIwNTdCBwMIAgeRxQGmATOitAOlBRoEpQUjBQAIAAiRxQQBBQEGAQekbggBCZHBAwEPkcUBkgABApHDAQACAAMAA5HDAQACAAMABJEABZEA--T3221233232319:F3321122120:A23121141:--1cfb45bf0658b316a41dcc9dfc2ef46a824db796--0--0--0--183",
        "Price": [
          {
            "kind": "FareSummary",
            "Oneway": "160",
            "Round": "320"
          },
          {
            "fareRevisionStatus": "none",
            "toLineIndex": "1",
            "fromLineIndex": "1",
            "kind": "Fare",
            "index": "1",
            "selected": "true",
            "Type": "Fare",
            "Oneway": "160",
            "Round": "320"
          },
          {
            "kind": "Teiki1Summary",
            "Oneway": "4850"
          },
          {
            "fareRevisionStatus": "none",
            "toLineIndex": "1",
            "fromLineIndex": "1",
            "kind": "Teiki1",
            "index": "1",
            "selected": "true",
            "Type": "Teiki1",
            "Oneway": "4850"
          },
          {
            "kind": "Teiki3Summary",
            "Oneway": "13820"
          },
          {
            "fareRevisionStatus": "none",
            "toLineIndex": "1",
            "fromLineIndex": "1",
            "kind": "Teiki3",
            "index": "1",
            "selected": "true",
            "Type": "Teiki3",
            "Oneway": "13820"
          },
          {
            "kind": "Teiki6Summary",
            "Oneway": "23260"
          },
          {
            "fareRevisionStatus": "none",
            "toLineIndex": "1",
            "fromLineIndex": "1",
            "kind": "Teiki6",
            "index": "1",
            "selected": "true",
            "Type": "Teiki6",
            "Oneway": "23260"
          }
        ],
        "Route": {
          "timeOther": "0",
          "timeOnBoard": "9",
          "exhaustCO2": "127",
          "exhaustCO2atPassengerCar": "974",
          "distance": "58",
          "timeWalk": "0",
          "transferCount": "0",
          "Line": {
            "stopStationCount": "3",
            "teiki3Index": "1",
            "teiki6Index": "1",
            "timeOnBoard": "9",
            "track": "railway",
            "exhaustCO2": "127",
            "fareIndex": "1",
            "exhaustCO2atPassengerCar": "974",
            "distance": "58",
            "teiki1Index": "1",
            "Name": "ＪＲ総武線・三鷹行",
            "Type": "train",
            "ArrivalState": {
              "no": "1",
              "Type": "normal",
              "Datetime": {
                "text": "2016-12-04T21:55:00+09:00",
                "operation": "today"
              }
            },
            "Destination": "三鷹",
            "TimeReliability": "onTimetable",
            "DepartureState": {
              "no": "16",
              "Type": "normal",
              "Datetime": {
                "text": "2016-12-04T21:46:00+09:00",
                "operation": "today"
              }
            },
            "Color": "255222000"
          },
          "Point": [
            {
              "Station": {
                "code": "22741",
                "Name": "新宿",
                "Type": "train",
                "Yomi": "しんじゅく"
              },
              "Prefecture": {
                "code": "13",
                "Name": "東京都"
              },
              "GeoPoint": {
                "longi": "139.42.11.0",
                "lati": "35.41.15.0",
                "longi_d": "139.703056",
                "gcs": "tokyo",
                "lati_d": "35.6875"
              }
            },
            {
              "Station": {
                "code": "22671",
                "Name": "高円寺",
                "Type": "train",
                "Yomi": "こうえんじ"
              },
              "Prefecture": {
                "code": "13",
                "Name": "東京都"
              },
              "GeoPoint": {
                "longi": "139.39.10.89",
                "lati": "35.42.7.59",
                "longi_d": "139.653027",
                "gcs": "tokyo",
                "lati_d": "35.70211"
              }
            }
          ]
        }
      },
      ...
```

このレスポンスから欲しい要素を取得してみます。例として、出発・到着駅、路線名と金額を表示してみます。

JavaScriptを使って上記のレスポンス(JSON)をパースしてそれっぽい結果表示を得てみます。
(説明を簡単にするため、乗り換えのない経路のみを表示する処理になっています)

```js 
#!/usr/bin/env node

var fs = require('fs');
var json_file = process.argv[2] == undefined ? 'result.json' : process.argv[2];

fs.readFile(json_file, 'utf8', function (err, json_str) {
	var json = JSON.parse(json_str);	
	var course = json.ResultSet.Course;	// ResultSet/Course要素を取得

	for (var i in course) {
		var price = course[i].Price[0].Oneway;	// 片道料金
		var line  = course[i].Route.Line;

		// 説明を簡略化するため、乗り換えのない経路のみ表示
		if (Array.isArray(line) == false) {

			var lineName = line.Name;			// 路線の名称
			var point = course[i].Route.Point;
			var from  = point[0].Station.Name;	// 出発駅
			var to    = point[1].Station.Name;	// 到着駅

			console.log('候補: ' + (parseInt(i) + 1));
			console.log('□ ' + from);
			console.log('│ ' + lineName);
			console.log('│ ' + price + '円');
			console.log('□ ' + to);
			console.log('');
		}
	}
});
```

上記JavaScriptを`sample.js`等の名前で保存し、以下のように実行してみます。

```shell-session
$ curl -s -XGET 'http://api.ekispert.jp/v1/json/search/course/extreme?key=<アクセスキー>&viaList=22741:22671' > result.json
$ ./sample.js result.json
```

実行結果は以下になります。経路探索のレスポンス(JSON)から探索結果の一覧を表示できています。

```
候補: 1
□ 新宿
│ ＪＲ総武線・三鷹行
│ 160円
□ 高円寺

候補: 2
□ 新宿
│ ＪＲ総武線・武蔵小金井行
│ 160円
□ 高円寺

候補: 3
□ 新宿
│ ＪＲ総武線・三鷹行
│ 160円
□ 高円寺

候補: 4
□ 新宿
│ ＪＲ総武線・武蔵小金井行
│ 160円
□ 高円寺
```

## まとめ

今日は[経路探索](http://docs.ekispert.com/v1/api/search/course/extreme.html)の話と、レスポンスのJSONを用いてJavaScriptで探索結果を表示する例を示しました。
これでWebAPIの使い方とレスポンスから欲しい要素を取り出すという一連の手順が説明できましたので、以降はより実用的な例を解説できればと思います。