PHPで最寄駅の混雑度予想をグラフ表示する

(1/1)
PHPで住所・ランドマークから最寄り駅を求める」では、HeartRails Geo APIを用いて最寄駅検索を行ったが、今回は、RapidAPI NAVITIME API を利用することで、住所やランドマークから最寄り駅(複数)を求め、その駅の混雑度予想をグラフ表示する。地図サービスとしてGoogleマップ、地理院地図、オープンストリートマップを選んで使えるようにする。グラフ表示はjqPlotを使用する。

目次

サンプル・プログラムの実行例

PHPで最寄駅の混雑度予想をグラフ表示する
Googleマップ表示

サンプル・プログラム

圧縮ファイルの内容
stationCongestion.phpサンプル・プログラム本体。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooCalendar.php暦・潮位計算クラス pahooCalendar。
暦・潮位計算クラスの使い方は「PHPで二十四節気・七十二候一覧を作成」「PHPで月齢を計算」「PHPで日出没・月出没・月齢・潮を計算」「PHPで潮位を計算する」などを参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
stationCongestion.php 更新履歴
バージョン 更新日 内容
1.0.0 2024/05/11 初版
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.3.3 2024/09/14 $this->NOMINATIM_EMAIL 追加
6.3.2 2024/02/14 getStaticMap() -- bug-fix
6.3.1 2023/07/09 bug-fix
6.3.0 2023/07/02 getPointsGSI()追加
6.2.0 2023/07/02 ip2address()追加
pahooCalendar.php 更新履歴
バージョン 更新日 内容
4.5.0 2024/03/17 ヒジュラ暦メソッドを追加
4.4.1 2024/03/17 getCabinetOfficeHolidayTable() -- bug-fix
4.4.0 2024/02/25 内閣府の祝日表を参照できるようにした
4.3.2 2023/02/11 getSolarTerm72() 表記改訂:水澤腹堅→水沢腹堅
4.3.1 2023/02/03 表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン,七十二候
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加
これ以外に、クライアント側で動作するJavaScriptライブラリとして、カレンダー入力するUIの jQuery UI:Datepicker と、グラフ表示の jqPlot を利用する。

サンプル・プログラムの流れ(メイン)

PHPで最寄駅の混雑度予想をグラフ表示する

準備:pahooGeoCode クラス

  37: class pahooGeoCode {
  38:     var $items;     //検索結果格納用
  39:     var $error;     //エラー・フラグ
  40:     var $errmsg;    //エラー・メッセージ
  41:     var $hits;      //検索ヒット件数
  42:     var $webapi;    //直前に呼び出したWebAPI URL
  43: 
  44:     //Google Cloud Platform APIキー
  45:     //https://cloud.google.com/maps-platform/
  46:     //※Google Maps APIを利用しないのなら登録不要
  47:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
  48:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
  49: 
  50:     //Yahoo! JAPAN Webサービス アプリケーションID
  51:     //https://e.developer.yahoo.co.jp/register
  52:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  53:     var $YAHOO_APPLICATION_ID = '*****************************';

地図描画や住所検索を行うために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図や住所検索として Google を利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、それぞれ参照されたい。

準備:地図サービスの選択

  70: //地図描画サービスの選択
  71: //    0:Google
  72: //    2:地理院地図・OSM
  73: define('MAPSERVICE', 0);
  74: 
  75: //住所検索サービスの選択
  76: //    0:Google
  77: //    1:Yahoo!ジオコーダAPI
  78: //   11:HeartRails Geo API
  79: //   12:OSM Nominatim Search API
  80: //   13:国土地理院ジオコーディングAPI
  81: define('GEOSERVICE', 0);
  82: 
  83: //逆ジオコーディングサービスの選択
  84: //    0:Google
  85: //    1:Yahoo!JAPAN
  86: //   11:HeartRails Geo API
  87: //   21:簡易ジオコーディングサービス
  88: define('REVGEOSERVICE', 1);

表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
住所検索サービスは、GoogleYahoo!JAPANHeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、GoogleYahoo!JAPANHeartRails Geo API簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。

RapidAPI NAVITIME API

RapidAPI NAVITIME API」は経路探索でお馴染みの [https://www.navitime.co.jp/:rtitle=NAVITIME] の機能をクラウド連携することができる。今回は、そのうち NAVITIME TransportNAVITIME Congestion Prediction の2つを使用する。
APIキーと利用機能をHTTPヘッダで渡し、各種パラメータをGET渡しすることで、JSON形式の応答を得ることができる。
利用にはRapidAPIのAPIキー(無償)が必要で、その入手方法は「RapidAPI - 各種WebAPIの登録方法」を参照されたい。
NAVITIME APIの一覧と利用料金は https://api-sdk.navitime.co.jp/api/specs/description/about_navitime_api.html をご覧いただきたい。月間500アクセスまでは無料で利用できる。

 422: class pahooRapidNAVITIME {
 423: 
 424: //RapidAPIキー
 425: //取得方法 https://www.paho.org/e-soul/webtech/php06/php06-01-02.shtm#RapidAPI
 426: const rapidApiKey = '**********************************************';

入手したAPIキーは、クラス pahooRapidNAVITIME の定数 rapidApiKey に代入する。

解説:最寄駅検索

リクエストURL
URL
https://navitime-transport.p.rapidapi.com/transport_node/around
HTTPヘッダ名
X-RapidAPI-Host navitime-transport.p.rapidapi.com
X-RapidAPI-Key APIキー

入力パラメータ
フィールド名 要否 内  容
coord 必須 最寄駅を取得したい緯度,経度(カンマ区切り)
type 省略可能 交通機関タイプ:station 鉄道駅,airport 空港,port 港,busstop 路線バスのバス停(要契約), shuttle_busstop 空港連絡バスのバス停, highway_busstop 高速バスのバス停(要契約)
limit 省略可能 応答の上限件数【デフォルト 10/最大 30】
term 省略可能 応答待ち時間(秒)【デフォルト 60/最大 1440】
datum 省略可能 測地系(wgs84またはtokyo)【デフォルト wgs84】
walk_speed 省略可能 歩行速度(km/h)【デフォルト 5/最小 3/最大 8】
応答データ(xml) response items id 駅/バス停のノードID name 駅/バス停の名称 ruby 駅/バス停の名称の読み仮名 types ノードタイプの一覧 address_name 住所の表示名 address_code 住所コード coord lat 駅/バス停の中心緯度 lon 駅/バス停の中心緯度 distance 出発地点緯度経度からの距離 link 路線情報 time 所要時間 gateway 出入り口の名称 items id 駅/バス停のノードID name 駅/バス停の名称 ruby 駅/バス停の名称の読み仮名 types ノードタイプの一覧 address_name 住所の表示名 address_code 住所コード coord lat 駅/バス停の中心緯度 lon 駅/バス停の中心緯度 distance 出発地点緯度経度からの距離 link 路線情報 reinses レインズコード time 所要時間 gateway 出入り口の名称

 447: /**
 448:  * 最寄駅検索 - RapidAPI NAVITIME API 2.0
 449:  * @param   double $latitude  緯度(世界測地系)
 450:  * @param   double $longitude 経度(世界測地系)
 451:  * @param   array  $items     応答情報を格納する配列
 452:  * @return  array(ヒットした施設数, メッセージ, APIのURL)
 453: */
 454: function getResultsTransportNodeAround($latitude, $longitude, &$items) {
 455:     //クラウドサービスの呼び出し
 456:     $curl = curl_init();
 457:     $url = 'https://navitime-transport.p.rapidapi.com/transport_node/around?coord=' . $latitude . ',' . $longitude . '&options=by_link';
 458:     curl_setopt($curl, CURLOPT_URL , $url);
 459:     curl_setopt($curl, CURLOPT_HEADER, 1) ; 
 460:     curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
 461:     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);   //結果を文字列で
 462:     curl_setopt($curl, CURLOPT_ENCODING, '');
 463:     curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
 464:     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
 465:     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
 466:     curl_setopt($curl, CURLOPT_TIMEOUT, 30);
 467:     curl_setopt($curl, CURLOPT_HTTPHEADER, [
 468:         'X-RapidAPI-Host: navitime-transport.p.rapidapi.com',
 469:         'X-RapidAPI-Key: ' . self::rapidApiKey
 470:     ]);
 471: 
 472:     $res1 = @curl_exec($curl);
 473:     $res2 = @curl_getinfo($curl);
 474:     $errmsg = curl_error($curl);
 475: 
 476:     if ($res1 == FALSE) {
 477:         return array(0, $errmsg, $url);
 478:     }
 479: 
 480:     //JSONデータを配列に格納する
 481:     $json = substr($res1, $res2['header_size']);
 482:     $results = json_decode($json);
 483: 
 484:     //応答のデータ構造チェック
 485:     if (! isset($results->items)) {
 486:         $errmsg = '検索に失敗しました';
 487:         return array(0, $errmsg, $url);
 488:     }
 489: 
 490:     //応答を配列へ代入する.
 491:     $cnt = 1;
 492:     foreach ($results->items as $node) {
 493:         //記号(A, B, C...)
 494:         $items[$cnt]['id']              = (string)$this->num2alpha($cnt);
 495:         //駅/バス停のノードID
 496:         $items[$cnt]['nodeId']          = (string)$node->id;
 497:         //駅/バス停の読み仮名
 498:         $items[$cnt]['ruby']            = (string)$node->ruby;
 499:         //住所の表示名
 500:         $items[$cnt]['address_name']    = (string)$node->address_name;
 501:         //住所コード
 502:         $items[$cnt]['address_code']    = (string)$node->address_code;
 503:         //緯度
 504:         $items[$cnt]['latitude']        = (float)$node->coord->lat;
 505:         //経度
 506:         $items[$cnt]['longitude']       = (float)$node->coord->lon;
 507:         //検索地点からの距離(メートル)
 508:         $items[$cnt]['distance']        = (int)$node->distance;
 509:         //検索地点からの所要時間(分)
 510:         $items[$cnt]['time']            = (int)$node->time;
 511:         //路線ID
 512:         $items[$cnt]['linkId']          = (string)$node->link->id;
 513:         //路線名称
 514:         $items[$cnt]['linkName']        = (string)$node->link->name;
 515:         //駅名の抽出(nameに路線名が混在しているので)
 516:         $arr = array();
 517:         $name = (string)$node->name;
 518:         if (preg_match('/\s.*?([^\s]*)$/ui', $name, $arr> 0) {
 519:             $name = isset($arr[1]) ? $arr[1: $name;
 520:         }
 521:         if (preg_match('/^([^\(]+)/ui', $name, $arr> 0) {
 522:             $name = isset($arr[1]) ? $arr[1: $name;
 523:         }
 524:         $items[$cnt]['title'] = $name;
 525:         //情報ウィンドウに表示する内容(HTML文)
 526:         $distance = number_format($items[$cnt]['distance']);
 527:         $items[$cnt]['description'] =<<< EOT
 528: {$items[$cnt]['title']}&nbsp;[{$items[$cnt]['linkName']}]&nbsp;{$distance}m
 529: EOT;
 530:         $cnt++;
 531:     }
 532:     return array($cnt - 1, $errmsg, $url);
 533: }

最寄駅検索(NAVITIME Transport )は、前述の通り、HTTPヘッダにAPIキーと利用機能を渡す必要があることから、cURL関数群を利用する。
応答データには、JSON形式データの前にヘッダ情報が混じっているので、 substr  関数を使ってJSON形式データのみを抽出してから、 json_decode 関数を使ってデコードし、配列 $items に格納する。

解説:混雑度予想

リクエストURL
URL
https://navitime-congestion-prediction.p.rapidapi.com/congestion_prediction/node
HTTPヘッダ名
X-RapidAPI-Host navitime-congestion-prediction.p.rapidapi.com
X-RapidAPI-Key APIキー

入力パラメータ
フィールド名 要否 内  容
id 必須 駅/バス停のノードID(最寄駅検索で取得したID)
date 必須 日付(yyyy-mm-dd形式;本日を含む未来日であること)
応答データ(xml) response items id 駅/バス停のノードID congestions time 予報日時(ISO8601形式;1時間ごと) prediction_rate 予測比率 search_count_rate 検索数の比率 congestions time 予報日時(ISO8601形式;1時間ごと) prediction_rate 予測比率 search_count_rate 検索数の比率 congestions time 予報日時(ISO8601形式;1時間ごと) prediction_rate 予測比率 search_count_rate 検索数の比率

 535: /**
 536:  * 指定した駅の混雑度予想 - RapidAPI NAVITIME API 2.0
 537:  * @param   string $id    駅/バス停のノードID
 538:  * @param   string $date  予想したい年月日(例:2024-05-06;未来日であること)
 539:  * @param   array  $items 応答情報を格納する配列
 540:  * @return  array(エラーメッセージ, APIのURL)
 541: */
 542: function getResultsCongestionNode($id, $date, &$items) {
 543:     //クラウドサービスの呼び出し
 544:     $curl = curl_init();
 545:     $url = 'https://navitime-congestion-prediction.p.rapidapi.com/congestion_prediction/node?id=' . $id . '&date=' . $date;
 546:     curl_setopt($curl, CURLOPT_URL , $url);
 547:     curl_setopt($curl, CURLOPT_HEADER, 1) ; 
 548:     curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
 549:     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);   //結果を文字列で
 550:     curl_setopt($curl, CURLOPT_ENCODING, '');
 551:     curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
 552:     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
 553:     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
 554:     curl_setopt($curl, CURLOPT_TIMEOUT, 30);
 555:     curl_setopt($curl, CURLOPT_HTTPHEADER, [
 556:         'X-RapidAPI-Host: navitime-congestion-prediction.p.rapidapi.com',
 557:         'X-RapidAPI-Key: ' . self::rapidApiKey
 558:     ]);
 559: 
 560:     $res1 = @curl_exec($curl);
 561:     $res2 = @curl_getinfo($curl);
 562:     $errmsg = curl_error($curl);
 563: 
 564:     if ($res1 == FALSE) {
 565:         return array(0, $errmsg, $url);
 566:     }
 567: 
 568:     //JSONデータを配列に格納する
 569:     $json = substr($res1, $res2['header_size']);
 570:     $results = json_decode($json);
 571: 
 572:     //応答のデータ構造チェック
 573:     if (! isset($results->items)) {
 574:         $errmsg = '検索に失敗しました';
 575:         return array(0, $errmsg, $url);
 576:     }
 577: 
 578:     //応答を配列へ代入する.
 579:     $cnt = 1;
 580:     foreach ($results->items[0]->congestions as $node) {
 581:         $dt = $node->time;
 582:         if (preg_match('/T([0-9]+)\:/i', (string)$node->time, $arr> 0) {
 583:             $hour = (int)($arr[1]);
 584:             if ($hour <2) {
 585:                 $hour +24;
 586:             }
 587:             $items[$hour] = (float)($node->prediction_rate);
 588:         }
 589:         $cnt++;
 590:     }
 591:     return array($cnt - 1, $errmsg, $url);
 592: }

混雑度予想(NAVITIME Congestion Prediction )は、HTTPヘッダにAPIキーと利用機能を渡す必要があることから、cURL関数群を利用する。
応答データには、JSON形式データの前にヘッダ情報が混じっているので、 substr  関数を使ってJSON形式データのみを抽出してから、 json_decode 関数を使ってデコードし、配列 $items に格納する。

 669: <td style="text-align:center;">{$val['id']}</td>
 670: <td><div class="station" onClick="getResultscongestionNode('{$val['nodeId']}')">{$val['title']}</div></td>
 671: <td>{$val['linkName']}</td>
 672: <td style="text-align:right;">{$distance}</td>

 162: <script>
 163: /**
 164:  * 駅名をクリックしたら,nodeIdを代入し,submitする→混雑度予想処理に入る.
 165:  * @param   string idNode  駅/バス停のノードID
 166:  * @return  なし
 167: */
 168: function getResultscongestionNode(idNode) {
 169:     document.getElementById('nodeId').value = idNode;
 170:     myform.submit();
 171: }
 172: </script>

上述の流れ図に示すように、駅/バス停のノードID($nodeID)があるかどうかで、最寄駅検索と混雑度予想の使い分けを行う。
まず初めに最寄駅検索を行い、そこで得られたノードID($val['nodeId'])を、一覧表の駅名をクリックするとJavaScript関数 getResultscongestionNode を呼び出すようにする。
JavaScript関数 getResultscongestionNode では、受け取ったノードIDをDOMオブジェクト nodeId(実体はhidden属性をもったテキストボックス)に格納し、submitすることで、このページを再読み込みしたことを同じ動きになる。

 706:         $json = json_encode($items);
 707:         $html =<<< EOT
 708: <body>
 709: {$jsmap}
 710: <h2>{$title} {$version}</h2>
 711: <form name="myform" method="post" action="{$myself}" enctype="multipart/form-data">
 712: 検索キー&nbsp;<input type="text" name="query" size="30" value="{$spot['query']}">
 713: <input type="submit" id="exec"  name="exec"  value="キーワード検索">&nbsp;
 714: <input type="submit" id="map"   name="map"   value="地図検索">&nbsp;
 715: <input type="submit" id="clear" name="clear" value="リセット">
 716: {$str_radio}<br>
 717: 年月日&nbsp;<input class="datepicker" type="text" id="yyyymmdd" name="yyyymmdd" size="10" value="{$date}" >
 718: 
 719: <input type="hidden" id="latitude"  name="latitude"  value="{$spot['latitude']}" >
 720: <input type="hidden" id="longitude" name="longitude" value="{$spot['longitude']}" >
 721: <input type="hidden" id="zoom"      name="zoom"      value="{$spot['zoom']}" >
 722: <input type="hidden" id="type"      name="type"      value="{$spot['type']}" >
 723: <input type="hidden" id="nodeId"    name="nodeId"    value="">
 724: <input type="hidden" id="mode"      name="mode"      value="{$mode}">
 725: <textarea style="display:none" id="json" name="json">{$json}</textarea>

また、最寄駅検索で得られた応答データは、再度、JSON形式にエンコードして、DOMオブジェクト json(実態はスタイルシートで不可視にしたtextareaタグ)に格納し、ページ再読み込み時に再び最寄駅検索APIを呼び出さないように工夫した。

その他の使用クラウド連携、JavaScriptライブラリなど

検索して得られた最寄駅をマップ上にマッピングし一覧表を表示する処理については、「PHPで住所・ランドマークから最寄り駅を求める」をご覧いただきたい。
日付をカレンダーから入力するJavaScriptライブラリ jQuery UI:Datepicker の使い方は、「PHPで日付入力:カレンダーから選択(WebAPI版)」をご覧いただきたい。
棒グラフ描画に使ったJavaScriptライブラリ jqPlot の使い方は、「PHPでNHK政治意識月例調査をグラフ表示」をご覧いただきたい。

参考サイト

(この項おわり)
header