駅すぱあとWebサービスの駅情報を地図上にマッピングしてみる

[ヴァル研究所 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/val) 1日目の記事です。

[駅すぱあとWebサービス](http://docs.ekispert.com/v1/)はWebAPI経由で駅すぱあとの情報を利用できるサービスです。
例えば、[駅情報](http://docs.ekispert.com/v1/api/station.html)、[駅簡易情報](http://docs.ekispert.com/v1/api/station/light.html) APIを使用すると、指定した駅に関する名称や緯度経度などの情報を取得できます。

```json
$ curl -s 'http://api.ekispert.jp/v1/json/station?key=<アクセスキー>&name=高円寺' | jq .
...
{
  "ResultSet": {
    "Point": [
      {
        ...
        "Station": {
          "Yomi": "こうえんじ",
          "Type": "train",
          "Name": "高円寺",
          "code": "22671"
        }
      },
      {
        ...
        "Station": {
          "Yomi": "こうえんじえきみなみぐち",
          "Type": {
            "detail": "local",
            "text": "bus"
          },
          "Name": "高円寺駅南口／関東バス",
          "code": "45641"
        }
      },
      ...
```

その都度アプリやツールを用意しなくても[curl](https://ja.wikipedia.org/wiki/CURL)のようなコマンドラインツールでパッと動作確認や調査できたりするのがWebAPIの魅力のひとつでもあるのですが、いかんせん「見栄えが地味...」という感じが拭えません。
そこで今回は、駅情報を地図上にマッピングするというサンプルを通じて、駅すぱあとWebサービスの(見栄えのよい)利用例を紹介したいと思います。

## 駅情報を地図上にマッピングするサンプル

サンプルプログラムはGitHubの[valsitoh/AdventCalendar](https://github.com/valsitoh/AdventCalendar)にて公開しています。
 * [AdventCalendar/2017/1201/](https://github.com/valsitoh/AdventCalendar/tree/master/2017/1201)

お手元の環境で動作させる際は、[駅すぱあとWebサービス](https://ekiworld.net/service/lp/webservice/)のアクセスキーを `index.html` と `app.js` 内の「<アクセスキーを入力してください>」の箇所に設定してください。

動作例は以下になります。

駅名を入力すると...
![img100.png](https://qiita-image-store.s3.amazonaws.com/0/152217/24bab905-e46e-6fdb-17cd-7b1cc0afb6b1.png)

地図上にピンが立ちます！ ...というサンプル的な動作です。
![img101.png](https://qiita-image-store.s3.amazonaws.com/0/152217/89aa6461-c56b-a99c-c14c-35e818f17f44.png)

## ブラウザ上で地図を扱う

今回のサンプルではWebブラウザ上で動作するアプリにしており、地図ライブラリとして[Leaflet](http://leafletjs.com)を使っています。
Leafletは弊社の[駅すぱあと路線図](http://rmap.ekispert.jp/sample/)のライブラリ、[Rosen JS](http://rmap.ekispert.jp/production/doc/document.html)でも用いられています。駅すぱあと路線図についても、のちのちAdvent Calendarでご紹介できればと思います。
また、Webアプリケーションフレームワークとしては、[AngularJS](https://ja.wikipedia.org/wiki/AngularJS)の1系(古い...)を使っています。

以下にサンプルコードの簡単な解説を示します。

### 地図の初期化

まずは地図の初期化です。HTMLのdiv要素で指定した要素に地図が表示される形になります。

```html
<div id="mapid" style="width: 100%; height: 600px;"></div>
```

Webページのロード時に `L.map(<id>, [option])` の形で地図を初期化します。オプションとしては最低限 `center` と `zoom` があれば十分かと思います。

```js
      $scope.mymap = L.map('mapid', {
        center: [35.68131, 139.767234], // 初期表示では東京駅を地図の中心にしています。
        zoom: 14
      });
      L.control.scale().addTo($scope.mymap);

      L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRq
        maxZoom: 18,
        attribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, ' +
        '<a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
        'Imagery © <a href="http://mapbox.com">Mapbox</a>',
        id: 'mapbox.streets'
      }).addTo($scope.mymap);
```


### 駅情報の入力と取得

駅名を入力した際のサジェスト表示は、[駅すぱあとWebサービス HTML5インターフェースサンプル](https://github.com/EkispertWebService/GUI)として提供されている、[駅名入力パーツ](https://github.com/EkispertWebService/GUI/wiki/html5_demo_3)を使用しています。

こちらもLeafletと同じく、div要素で指定した要素を初期化時に処理して駅名入力時のサジェスト表示などを行えるようにします。

```html
<div id="station1" style="width:100%;"></div>
```

```js
      $scope.station_app = new expGuiStation(document.getElementById("station1"));
      $scope.station_app.dispStation();
```

これで先のスクリーンショットのような感じで、駅名入力時に候補がサジェスト表示されるようになります。

#### 駅情報の取得

駅名入力パーツで駅名が確定したら、駅すぱあとWebサービスのAPIを呼び出して駅情報を取得します。
(駅名入力パーツも内部的には駅すぱあとWebサービスのAPIを呼び出しているのですけどね...(^_^;)

処理内容は以下になります。実質的にはWebAPIを呼び出すと変数にJSONオブジェクトが入ってくるので、それを取り出しているだけです。
ただ、駅すぱあとWebサービスのレスポンスはちょっと構造が複雑なため、実際に情報を取り出す際は、[WebAPIリファレンス](http://docs.ekispert.com/v1/)と実際に返されたレスポンスの構造を見ながらデータの取得処理を実装すると良いかと思います。

それと、駅すぱあとWebサービスが返す緯度経度情報ですが、デフォルトでは日本測地系になっています。これに対し、Leafletでの緯度経度指定は世界測地系になっているため、変換処理が必要になる点に注意してください。とはいえ実際には以下のコードにあるような変換式を通すだけでOKです。

```js
      var url = 'http://api.ekispert.jp/v1/json/station?key=' + $scope.ACCESS_KEY;
      url += '&code=' + $scope.station_app.getStationCode();

      // 駅すぱあとWebサービスの駅情報(/station)APIを呼び出す
      $http({
        method: 'GET',
        url:    url
      }).success(function(data, status, headers, config) {
        // 緯度経度の測地系を日本測地系から世界測地系に変換する。
        // (leaflet(地図描画ライブラリ)に渡す緯度経度は世界測地系での指定になるため)
        var base_lati  = data.ResultSet.Point.GeoPoint.lati_d;  // 緯度(日本測地系)
        var base_longi = data.ResultSet.Point.GeoPoint.longi_d; // 経度(日本測地系)

        var wgs_lati  = base_lati  - base_lati * 0.00010695  + base_longi * 0.000017464 + 0.0046017;
        var wgs_longi = base_longi - base_lati * 0.000046038 - base_longi * 0.000083043 + 0.01004;

        var new_station = {
          code: data.ResultSet.Point.Station.code,  // 駅コード
          name: data.ResultSet.Point.Station.Name,  // 駅名
          yomi: data.ResultSet.Point.Station.Yomi,  // よみがな
          prefecture: data.ResultSet.Point.Prefecture.Name,  // 都道府県
          lati:  wgs_lati,  // 緯度(世界測地系)
          longi: wgs_longi, // 経度(日本測地系)
          pin_on_map: false
        };

        $scope.stations.push(new_station); // 駅名の配列に追加する。
      }).error(function(data, status, headers, config) {
        console.log('error. status = ' + status);
      });
```

### 地図上にピンを立てる

いよいよ地図上にピンを立ててみます。これも単にLeafletの関数を呼ぶだけなので別段難しいことはありません。

```js
        var marker = L.marker([$scope.stations[i].lati, $scope.stations[i].longi]);
        marker.addTo($scope.mymap).bindPopup($scope.stations[i].name).openPopup();
        $scope.markers.push(marker);
```

これで地図上にピンが表示されました。
![img103.png](https://qiita-image-store.s3.amazonaws.com/0/152217/ef7fd2c6-9932-3105-78f2-9c3343f6bc7b.png)

とくにバス停などでは、各停留場の位置が把握できるのでサンプルでありながらも意外と便利そうです(実際に作っていてそう思いました...)。
![img104.png](https://qiita-image-store.s3.amazonaws.com/0/152217/964cbb64-699c-1d96-221d-096efe20d3ce.png)

### まとめ

駅すぱあとWebサービスとLeafletを使って駅情報を地図上にマッピングする簡単な手順を紹介しました。
経路探索結果にも駅情報は含まれているため、経路探索結果と緯度経度情報を地図上にマッピングするという応用例もありそうです。

明日のAdvent Calendar 2日目では、今日紹介したLeafletでの地図マッピングを応用して、駅すぱあとWebサービスの[範囲探索](http://docs.ekispert.com/v1/api/search/multipleRange.html)がより便利になったという話を書こうと思います。