JavaScriptでのNAVITIME_APIの利用方法

<p>NAVITIME_APIは、公式ドキュメントに各機能の詳しい説明はあるものの、とりあえず使ってみたいという方や、自分のような初心者が、ある程度使えるようにするためのものがなかったので、自分にとっての備忘録も兼ねて、今回の記事で紹介させていただきます。今回はNAVITIME_APIの/route_transitについて扱います。</p>
<h1>NAVITIME_API/route_transitとは？</h1>
<p>公式の説明を確認すると、<br><br>
公共交通手段＋徒歩で、ドアtoドアの最適な移動経路・料金を算出します。駅の出入り口情報なども合わせて取得できます<br><br>
という説明が書かれています。<br>
この説明はかなり簡潔になっており、実際には移動にかかる時間や経由地、電車の出発、発着時間等様々な情報を取得できます。また、この値を追加することでAPIのみで地図上にルートを表記することも可能です。
<h3>注意点</h3>
今回紹介する、RapidApiを経由してAPIキーを取得する方法では、バスは検索できず、電車も実際の時刻表に則った時間ではなく、平均時間で表示されます。これらの制限を解除して使うには<a href = "https://api-sdk.navitime.co.jp/api/pr/?gad_source=1&gclid=Cj0KCQjwpP63BhDYARIsAOQkATYKnW9lqsEHBJ3My9gCavLEbQKqbPAi3ZEGqVwkOZ0OQeglQBo2wUgaAmgXEALw_wcB">《NAVITIME》 APIサービス</a>のページから申請してAPIキーを取得する必要があるようです。また、ルートを取得できる回数には限りがあり、無料分は500回と少ないので気をつけた方がいいです。（自分は制限に達してしまい、開発が止まりました。）</p>
<h1>導入方法</h1>
<p>使用するためにはまず、先述した通り、APIキーを取得する必要があります。今回は先述した通り、RapidApiを経由してAPIキーを取得します。</p>
<h4>1.RapidApiにログイン、登録</h4>
<p><a href = "https://rapidapi.com/hub">https://rapidapi.com/hub</a>に飛んで画面上部にある、Sign Upをクリックして画面通りに登録を進めます。すでにRapidApiのアカウントがある人はSign Inからログインしてください。</P>
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/63f7b41b-52f4-4c90-e6ab-7612dc17bbbf.png">
<h4>2.RapidApi内で検索</h4>
<p>上部検索バーでnavitimeと検索し、一番上に出てきた、NAVITIME Route(totalnavi)を選択します。</p>
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/871fe64b-8cc8-fbbe-563d-c3c3967ec7a2.png">
<h4>3.APIキー取得</h4>
<p>右上のSubscribe to Testをクリックし、Basicプランを選択した後、前のページに戻ってX-RapidAPI-Keyの欄に記載のあるAPIキーを取得します。</p>
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/10d9d4d2-e4a5-32a8-d55a-babc369e4040.png">
<h1>ルート検索方法</h1>
<p>ここまでこれば後は値を入力するだけです。ルート検索に必須になる要素は<strong>スタート地点とゴール地点の緯度と経度、出発時間もしくは到着時間</strong>です。</p><br>
<h3>必要な値</h3>
<h4>・スタート地点とゴール地点</h4>
<p>緯度と経度を並べるだけです。<br><strong>例：38,136 (緯度38、経度136)</strong></p><br>
<h4>・出発時間もしくは到着時間</h4>
<p><strong>YYYY-MM-DDTHH:MM</strong>の形式での時間の入力が必要となります。<strong>inputのtype="datetime-local"</strong>で取得できる値がこの形式となっています。</p></br>

<h3>実際に取得</h3>
<h4>・URL</h4>
<p>先ほど紹介した必須要素を組み込みます。</p>

```javascript
//start、goalの値を東京駅と大阪駅、出発時刻を2024年1月1日10：00に指定
let requestUrl = "https://navitime-route-totalnavi.p.rapidapi.com/route_transit?start=35.681236,139.767125&goal=34.702485,135.495951&start_time=2024-01-01T10:00:00"
```
<h4>・requestUrlを使用して検索</h4>
<p>fetch関数を使い、requestUrl に対してGETリクエストを送信する関数します。また、その際にはリクエストに付加される情報（HTTPメソッドやヘッダー）を定義して組み込む必要があります。</p>

```javascript
//APIにデータを取得するリクエスト（GETリクエスト）を送信することを指定し、
//Navitime APIを利用するための認証にAPIキーを入力
const options = {
    method: 'GET',
    headers: {
        'x-rapidapi-key': 'your_api_key',
        'x-rapidapi-host': 'navitime-route-totalnavi.p.rapidapi.com'
    }
};
//GETリクエストを送信
fetch(requestUrl, options)
    .then(response => {
        return response.json();
    })
    .then(data => {
        // 検索結果取得完了
        console.log(data.items);
});
```
<p>今回はconsole.log(data.items);で検索結果を呼び出しています。console.log(data);で実行するとdata.unitという値も取得することができますが、こちらは検索方法、表示方法（単位等）についての記述となっています。</p>
<h1>検索結果</h1>
<p>上の検索で取得できるルートの数は5つとなっており、<strong>items</strong>という配列に含まれています。itemsの下には<strong>sectionsとsummary</strong>というアイテムがあります。</p>
<h3>summary</h3>
<p>スタート地点とゴール地点、ルート全体でかかる時間、料金、発着時間に付いての情報が得られます。以下に取得できるデータの一例を示します。</p>

```
summary: 
    goal: 
        coord: {lat: 34.702485, lon: 135.495951}
        name :  "goal"
        type: "point"
        [[Prototype]]: Object
    move: 
        distance: 556903
        fare: {unit_1: 4960, unit_2: 5810, unit_0: 8910, unit_3: 10680}
        from_time: "2024-01-01T10:00:00+09:00"
        move_type : (3) ['local_train', 'superexpress_train', 'walk']
        time: 190
        to_time:  "2024-01-01T13:10:00+09:00"
        transit_count: 1
        type: "move"
        walk_distance: 503
        [[Prototype]]: Object
    no: "1"
    start:
        coord: {lat: 35.681236, lon: 139.767125}
        name: "start"
        type: "point"
```


<h3>sections</h3>
<p>sectionsには、typeの指定がpointのものとmoveのものが交互に入っており、<strong>pointには地点情報が、moveにはpoint間をつなぐ移動方法</strong>が含まれています。</p>
<h4>・point</h4>
<p>type = "point"から得られる情報は緯度経度、地点名、駅等に入る入口などです。以下に取得できるデータの一例を示します。</p>

```
coord: {lat: 35.680576, lon: 139.76861}
gateway: "八重洲中央口"
name: "東京"
node_id: "00006668"
node_types: ['station']
type: "point"
```

<p>node_typesは配列で複数の値が返ってくることもあります。</p>
<h4>・move</h4>
<p>以下にtype = "move"から得られる情報は距離、移動方法、発着時間などです。以下に取得できるデータの一例を示します。</P>

```
distance: 3800
from_time: "2024-01-01T13:00:00+09:00"
line_name: "ＪＲ京都線・ＪＲ神戸線各停"
move: "local_train"
time: 6
to_time: "2024-01-01T13:06:00+09:00"
transport: {fare: {…}, getoff: '中', name: 'ＪＲ京都線・ＪＲ神戸線各停', fare_season: 'normal', …}
type: "move"
```

<p>transportは公共交通期間を使用した際に得られる、その路線に関する料金等の様々な情報が含まれています。以下に取得できるデータの一例を示します。</p>

```
transport: 
    company: {id: '00000002', name: 'ＪＲ西日本'}
    fare : {unit_0: 8910, unit_254: 0}
    fare_break: {unit_0: true, unit_254: true}
    fare_detail: (2) [{…}, {…}]
    fare_season: "normal"
    getoff: "中"
    id: "00000352"
    links: 
    Array(1)
        0: {id: '00000085', name: 'ＪＲ東海道本線（米原-神戸）', direction: 'down', destination: {…}, from: {…}, …}
    length: 1
    [[Prototype]] : Array(0)
    name: "ＪＲ京都線・ＪＲ神戸線各停"
    type: "普通"
```


<h3>実際に表示してみる</h3>
<p>紹介してきた要素からわかりやすくルート情報を取り出してみます。</p>

```html
<body>
    <div id = "result"></div>
</body>
```
    
```javascript
    document.addEventListener('DOMContentLoaded', function() {
    //startを東京駅、goalを大阪駅、出発時刻を2024年1月1日10：00に指定
    let requestUrl = "https://navitime-route-totalnavi.p.rapidapi.com/route_transit?start=35.681236,139.767125&goal=34.702485,135.495951&start_time=2024-01-01T10:00:00"
    
    //APIにデータを取得するリクエスト（GETリクエスト）を送信することを指定し、
    //Navitime APIを利用するための認証にAPIキーを入力
        const options = {
            method: 'GET',
            headers: {
                'x-rapidapi-key': 'your_api_key',
                'x-rapidapi-host': 'navitime-route-totalnavi.p.rapidapi.com'
            }
        };
    
    
        fetch(requestUrl,options)
            .then(response =>{
                return response.json();
            })
        
            .then(data => { 
                //データを入れ込む値を作成
                let resultHTML = "";
    
                //今回は1つ目のルートデータのみを使用
                let route = data.items[0];
                
                // 出発・到着時刻
                resultHTML += `<h3>${route.summary.move.from_time}➡${route.summary.move.to_time}<h3>`;
                
                // summaryからの抜き出し
                if(route.summary){
                    let totalTime = route.summary.move.time; // 合計でかかる時間
                    resultHTML += `<h4>(${totalTime}分)</h4>`;
                    if(route.summary.move && route.summary.move.fare && route.summary.move.fare.unit_0){
                        resultHTML += `<h4>¥${route.summary.move.fare.unit_0}</h4>`; // 合計の運賃
                    }
                }
                
                resultHTML +=`<hr>`;
                
                // section要素をループさせる
                route.sections.forEach(function(section) {
                    let type = section.type;
    
                    //type === "move"の処理
                    if(type === "move"){
                        
                        //出発時刻
                        resultHTML += `<h4>${section.from_time}発</h4>`;
                        
                        //移動手段
                        if(section.move === "superexpress_train"){
                            resultHTML += `<h4>新幹線(${section.line_name})</h4>`;    
                        }else if(section.move === "local_train"||section.move === "rapid_train"){
                            resultHTML += `<h4>電車(${section.line_name})</h4>`;    
                        }else{
                        //移動方法
                        resultHTML += `<h4>${section.line_name}</h4>`;
                        }
                        
                        //電車を使用していた場合の料金表記
                        if(section.move === "superexpress_train" || section.move === "local_train"||section.move === "rapid_train"){
                            if(section.transport.fare){
                                if(section.transport.fare.unit_0){
                                    resultHTML += `<h4>${section.time}分   ${section.transport.fare.unit_0}円</h4>`;
                                }else if(section.transport.fare.unit_1){
                                    resultHTML += `<h4>${section.time}分   ${section.transport.fare.unit_1}円</h4>`;
                                }
                            }
                            
                        }else{
                            resultHTML += `<h4>${section.time}分</h4>`;
                        }
                        
                        
                        //到達時刻
                        resultHTML += `<h4>${section.to_time}着</h4>`;
    
                    //type === "point"の処理
                    } else if(type === "point"){
                        resultHTML += `<h3> ${section.name}</h3>`;
                    }
                    resultHTML += "<hr>";
                })
                
    
            //HTMLのid = "result"に結果を表示
            document.getElementById("result").innerHTML = resultHTML;
        
            })
            .catch(error => {
                console.error('エラーが発生しました:', error);
                document.getElementById('result').innerHTML = '<p>経路情報の取得に失敗しました。</p>';
            });
    });
```

<p>これをそのまま実行した結果が以下になります。</p>
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/88986fd0-8916-dfbf-3063-e5ddd30b0996.png">
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/7f231fdb-0a8a-c9d1-ee4e-e5e9fd61aad5.png">
<p>東京駅から大阪駅までのルートを取得することができました！</p>

<h1>地図上にルートを表示させる方法</h1>
<p>ルート情報はAPIのリクエストURLに"shape=true"を入れるだけでデータを取得することができます。</p>

```
let requestUrl = "https://navitime-route-totalnavi.p.rapidapi.com/route_transit?start=35.681236,139.767125&goal=34.702485,135.495951&start_time=2024-01-01T10:00:00&shape=true"
```
こうすることで、取得できる情報に上で紹介したsummaryとsectionsの他に、shapesが追加されます。このデータはGeoJSON形式となっており、適当な地図に取り込ませれば自動的にルートを形成してくれます。今回はGoogleMapを使ってルートを表示してみます。

```html
<body>
    <!--マップ表示エリアを指定-->
    <div id="mapArea" style="width:800px; height:400px;"></div>
</html>
```
```javascript
<script src="https://maps.googleapis.com/maps/api/js?key=your_api_key&callback=initMap" defer></script>

<script>
//グローバルスコープでmapを定義
let map;
//コールバックでマップを読み込む
function initMap(){
   //マップ読み込み
    map = new google.maps.Map(document.getElementById("mapArea"), {
            zoom: 5,
            center: new google.maps.LatLng(36,138),
            mapTypeId: google.maps.MapTypeId.ROADMAP
        });  
}

document.addEventListener('DOMContentLoaded', function() {
        
    //start、goalの値を東京駅と大阪駅、出発時刻を2024年1月1日10：00に指定
    let requestUrl = "https://navitime-route-totalnavi.p.rapidapi.com/route_transit?start=35.681236,139.767125&goal=34.702485,135.495951&start_time=2024-01-01T10:00:00&shape=true"
    
    //APIにデータを取得するリクエスト（GETリクエスト）を送信することを指定し、
    //Navitime APIを利用するための認証にAPIキーを入力
    const options = {
        method: 'GET',
        headers: {
            'x-rapidapi-key': 'your_api_key',
            'x-rapidapi-host': 'navitime-route-totalnavi.p.rapidapi.com'
        }
    };


    fetch(requestUrl,options)
        .then(response =>{
            return response.json();
        })
    
        .then(data => { 

            //データを地図に追加
            map.data.addGeoJson(data.items[0].shapes);
            
        })
        .catch(error => {
            console.error('エラーが発生しました:', error);
            document.getElementById('result').innerHTML = '<p>経路情報の取得に失敗しました。</p>';
        });
});

        
</script>

```

これをそのまま実行した結果が以下になります。
<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/aee9b1ec-30d9-0263-ffb3-c671434e58e4.png">
<p>今回追加した要素は、最初にマップを読み込む操作と、</p>

```javascript
//データを地図に追加
map.data.addGeoJson(data.items[0].shapes);
```

<p>のみです。飾り気はありませんが、ルート表示だけならこれだけで実現可能です。</p>
<h1>指定できる値の紹介</h1>
<p>指定できる要素は非常に多いため、今回は個人的におすすめする２つを紹介します。すべて見たい場合は<a href = "https://api-sdk.navitime.co.jp/api/specs/api_guide/route_transit.html">仕様書</a>をチェックしてみてください。</p>
<h3>via</h3>
<p>経由地を指定できます。最大、10箇所まで指定できます。形式は以下の通りです。</p>

```javascript
let via = [{"lat":35,"lon":139},{"lat":36,"lon":138}];
```

<p>配列で、latで緯度を、lonで経度を指定します。これに加えて、<strong>stay-timeで滞在時間を指定</strong>することもできます。これを発着時点等と同様にリクエストUrlに組み込むのですが、その際にはJSON形式に変更した上でエンコードする必要があります。</p>

```javascript
//stay-timeも組み込んだ例
let requestUrl = `https://navitime-route-totalnavi.p.rapidapi.com/route_transit?start=35.681236,139.767125&goal=34.702485,135.495951&start_time=2024-01-01T10:00:00&shape=true&via=${encodeURIComponent(JSON.stringify([{"lat":35,"lon":139,"stay-tyme":'5'},{"lat":36,"lon":138}]))}`;

```
<p>viaの指定時に<strong>　""　の代わりに　''　を使ったり、各パラメータの間に空白を入れたりするとエラーが発生する</strong>ので気をつけましょう。また、デフォルトだとviaの配列順でルートが検索されますが、<strong>クエリにvia_type=optimalを組み込むと最適な順番に並び替えた検索結果を取得</strong>できます。</p>

<h3>shape_color</h3>
<p>マップに表示したルート情報に路線ごとの色等の装飾をつけることができます。リクエストURLに<strong>shape_color=railway_line</strong>を組み込むことでデータを取得できます。</p>

```javascript
//shape_colorを適用するためのルート情報も取得する
let requestUrl = "https://navitime-route-totalnavi.p.rapidapi.com/route_transit?start=35.681236,139.767125&goal=34.702485,135.495951&start_time=2024-01-01T10:00:00&shape=true&shape_color=railway_line"
```

<p>この結果得られる情報は、<strong>shapes.feature.inlineと、shapes.feature.outline</strong>に入っています。今回はとりあえずinlineの方だけを使って装飾してみます。</p>

```javascript
// スタイルの設定
map.data.setStyle(function(feature){
    const inline = feature.getProperty('inline');
    return {
        strokeColor: inline.color,
        strokeWeight: inline.width,
        strokeOpacity: inline.opacity,
        strokeLineCap: inline.strokelinecap,
        strokeLineJoin: inline.strokelinejoin,
    };
});
```

<p>取得するデータは地図上でルートを表示しているfeatureを自動的に参照してくれます。「地図上でルートを表示させる方法」で紹介したコードに上のコードを組み込んで実行した結果が以下になります。</p>

<img src = "https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3867343/a0566259-abbc-db66-36d1-50a9461e8db8.png">
<p>視覚的にわかりやすくなりましたね。</p>

<h1>まとめ</h1>
<p>NAVITIME APIは取得できる情報が多く、カスタマイズ性があるAPIになっています。地理情報を扱うAPIとしてはGoogleMap APIが有名ですが、NAVITIME APIは今回ルート表示でやったようにGoogleMap APIと組み合わせて使用することも可能です。<br>長文になってしまいましたが、最後まで読んでいただきありがとうございました。</p>