はじめに
前回「日経ソフトウエア 2022年 11月号」の特集記事のプログラムを実行して動作を確認した後、サーバーの処理を追っかけてみました。
今回はフロントエンドの処理を追っかけて行きます。
フロントエンドはこんな画面です。

フロントエンドの処理 index.html
起動時の処理
- ビューポートの初期サイズ設定
- JQueryの組み込み
- Bootstrapの組み込み
- Maps JavaScript APIの読み込み
- マップを表示する<div id=”map”>の高さを800pxに設定
- Gooleマップ関連の初期化
- イベントハンドラ登録
1. ビューポートの初期サイズ設定
<meta name="viewport" content="width=device-width, initial-scale=1">ビューポートの幅を端末の画面幅に合わせズーム率を等倍に設定
2. JQueryの組み込み
フレームワークとしてBootstrapを使用するので、JQueryは必須になります。
3. Bootstrapの組み込み
フレームワークとしてBootstrapを使用しています。
4. Maps JavaScript APIの読み込み
<script src="https://maps.googleapis.com/maps/api/js?key=[取得したAPIキー]" type="text/javascript"></script>記事が古いので「ダイレクトスクリプト」でAPIを読み込んでいますが今は「インラインブートストラップローダ」が推奨されているそうです。「インラインブートストラップローダ」なら下記の記述になります。
<script>
(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
key: "Y取得したAPIキー",
v: "weekly",
// Use the 'v' parameter to indicate the version to use (weekly, beta, alpha, etc.).
// Add other bootstrap parameters as needed, using camel case.
});
</script>5. マップを表示する<div id=”map”>の高さを800pxに設定
<style>
#map {
height: 800px;
}
</style>6. Gooleマップ関連の初期化
map = new google.maps.Map(document.getElementById('map'), {
zoom: 14,
center: new google.maps.LatLng(35.710357, 139.693262),
mapTypeId: google.maps.MapTypeId.ROADMAP // 基本的な地図
});- zoom: ズームレベルを指定しています。14は通りが見えるサイズ。
- center: 地図の中心位置を指定しています。
- mapTypeId: 地図の種類を指定しています。ROADMAPは基本的な地図です。
他に航空写真・航空写真複合ビュー・地形があります。
下記コードは、交通状況レイヤを取得しています。
trafficLayer = new google.maps.TrafficLayer(); 7. イベントハンドラの登録
画面には以下のようなフォーム要素があります。

- 「バス停検索」テキストボックス
- 「選択されたバス停」リストボックス
- 「路線名」チェックボックス
- 「交通状況を表示」チェックボックス
JQuryを使って、それぞれの要素にイベントハンドラを登録しています。
| 要素 | イベント | 呼び出し関数 |
|---|---|---|
| バス停検索 | keyup | onChangeBusstopPoleInput |
| 選択されたバス停 | change | onChangeBusstopPoleSelect |
| 路線名 | change | onChangeDisplayTrafficJam |
| 交通状況を表示 | input | onChangeBusRoutesCheckbox |
各フォーム要素のイベントハンドラの動き
1. 「バス停検索」テキストボックスにテキスト入力
このテキストボックスには検索したいバス停名を入力します。
文字が入力されるとバックエンドの「GET busstopPoles」にリクエストを送ります。
バックエンドではバス停名または読み仮名が部分一致するバス標柱情報データを抽出してJSONデータを返します。
返ってくるデータはこんな感じです。
// バス停一つ分のデータ構造
{"sameAs":"odpt.BusstopPole:Toei.Tenjincho.957.1", // バス停ID
"title":"天神町", // バス停名
"kana":"てんじんちょう", // バス停名ひらがな
"lat":35.730858, // バス停の緯度
"lng":139.494166, // バス停の緯度
"busroutePattern":["odpt.BusroutePattern:Toei.Ume70.28008.1", // 経由バス系統ID
"odpt.BusroutePattern:Toei.Ume70.28009.1",
"odpt.BusroutePattern:Toei.Ume70.28012.1"]
},返ってきたデータを元に「選択されたバス停」リストボックスを作成します。
リストボックスにはデータの「sameAs」と「tilte」を使用しています。

2. 「選択されたバス停」リストボックスの項目選択
「バス停検索」テキストボックスに文字が入力されると、このリストボックスにバス停がセットされます。
バス停を選択すると以下の処理を行います。
1.地図上に既にバス運行状況が表示されている場合、一度クリア
・バスリアルタイム情報消去
・バス路線を消去
・バス停マーカーを消去
・バス路線上のバス停マーカーを消去
2.選択したバス停を示すマーカー(赤三角形)を地図上に表示
Maps JavaScript APIを使用してマーカーを地図上に表示します。
// 経度と緯度を指定してマップ上の地点を表すオブジェクトを作成
const latLang = new google.maps.LatLng(lat, lng);
// 地図上にマーカーを配置
const busstopPoleMarker = new google.maps.Marker({
position: latLang,
map,
icon: {
fillColor: '#FF0000',
// 後方(進行方向とは逆)を向いた閉じた矢印の記号
path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
scale: 3,
strokeColor: '#FF0000',
},
});3.マーカー上に吹き出しでバス停名を表示
バス停名はMaps JavaScript APIのポップアップウィンドウを使用して表示します。
// 地図上にポップアップするウィンドウを作成
(new google.maps.InfoWindow({
content: `<div>${title}</div>`, // 情報ウィンドウに表示するコンテンツ
shouldFocus: false, // キーボードフォーカスをウィンドウ内に移動させない
disableAutoPan: true, // 地図の表示範囲を自動的に調整する「自動パン」機能を無効
})).open({ // 作成したウィンドウを表示
anchor: busstopPoleMarker, // マーカーに紐づける
map, // 表示対象地図オブジェクト
});表示されたマーカーとポップアップのバス停名です

4.バックエンドにリクエストを送りバス停を経由するバス運行系統一覧を取得
バックエンドの「GET busroutePatterns」にリクエストを送ります。
バックエンドでは選択されたバス停を含むバス運行系統情報データを抽出し、標柱情報とマッピングしたオブジェクト配列をJSONデータにして返します。
返ってくるデータはこんな感じです。
// バス運行系統情報オブジェクト一つ分のデータ構造
{"sameAs": "odpt.BusroutePattern:Toei.T05-1.72414.1", // バス系統ID
"title":"都05-1 東京駅丸の内南口行", // バス路線名称
"coordinates":[{"lng":139.774971,"lat":35.64798}, // バス路線の緯度・経度配列
・
・
・
{"lng":139.765532,"lat":35.680137}],
"busstopPoles": [ // 経由するバス停情報配列
{"sameAs":"odpt.BusstopPole:Toei.Tenjincho.957.1", // バス停ID
"title":"天神町", // バス停名
"kana":"てんじんちょう", // バス停名ひらがな
"lat":35.730858, // バス停の緯度
"lng":139.494166, // バス停の緯度
"busroutePattern":["odpt.BusroutePattern:Toei.Ume70.28008.1", // 経由バス系統ID
"odpt.BusroutePattern:Toei.Ume70.28009.1",
"odpt.BusroutePattern:Toei.Ume70.28012.1"]
}]
},5.バス運行系統チェックボックスを表示
取得したデータを元にチェックボックスを作成して表示しています。
「sameAs」がvalueの値に「title+(sameAs)」が選択枝の名称になります。
<input class="form-check-input" type="checkbox" name="busroute_pattern_checkbox" value="odpt.BusroutePattern:Toei.Ume70.28012.1">
梅70 花小金井駅北口行 (odpt.BusroutePattern:Toei.Ume70.28012.1)表示されたチェックボックスです。

3.「路線名」チェックボックスにチェック
「選択されたバス停」リストボックスのバス停を選択すると、そのバス停が含まれる路線名のチェックボックスが表示されます。
チェックボックスを選択すると以下の処理が行われます。
1.チェックされている路線のデータを抽出
・input要素のチェックされているものを抽出し配列にセット
2.地図上に既にバス運行状況が表示されている場合、一度クリア
・バスリアルタイム情報消去
・バス路線を消去
・バス路線上のバス停マーカーを消去
3.「1.」で作成した配列に対して、順次以下の処理を行います
(1)バス運行系統一覧データからチェックされたデータを抽出
・抽出元のデータは「選択されたバス停」リストボックス選択時にバックエンドの「GET busroutePatterns」にリクエストを送り取得したデータです。
// バス運行系統情報オブジェクト一つ分のデータ構造
{"sameAs": "odpt.BusroutePattern:Toei.T05-1.72414.1", // バス系統ID
"title":"都05-1 東京駅丸の内南口行", // バス路線名称
"coordinates":[{"lng":139.774971,"lat":35.64798}, // バス路線の緯度・経度配列
・
・
・
{"lng":139.765532,"lat":35.680137}],
"busstopPoles": [ // 経由するバス停情報配列
{"sameAs":"odpt.BusstopPole:Toei.Tenjincho.957.1", // バス停ID
"title":"天神町", // バス停名
"kana":"てんじんちょう", // バス停名ひらがな
"lat":35.730858, // バス停の緯度
"lng":139.494166, // バス停の緯度
"busroutePattern":["odpt.BusroutePattern:Toei.Ume70.28008.1", // 経由バス系統ID
"odpt.BusroutePattern:Toei.Ume70.28009.1",
"odpt.BusroutePattern:Toei.Ume70.28012.1"]
},
・
・
・
{"sameAs": "odpt.BusstopPole:Toei.HanaKoganeiStationKitaguchi.2595.1",
"title": "花小金井駅北口",
"kana": "はなこがねいえききたぐち",
"lat": 35.726457,
"lng": 139.51309,
"busroutePattern": ["odpt.BusroutePattern:Toei.Ume70.28008.1",
"odpt.BusroutePattern:Toei.Ume70.28009.1",
"odpt.BusroutePattern:Toei.Ume70.28012.1"]
}
]
},(2)バス路線の表示
・データの coordinatesに入っているバス路線の緯度・経度配列を利用してMaps JavaScript APIで地図上にバス路線を描画します。
const busroutePath = new google.maps.Polyline({
path: coordinates, // バス路線の緯度・経度配列
strokeColor: '#FF0000', // 線の色
strokeOpacity: 0.5, // 線の透明度
strokeWeight: 5, // 線の幅
});
busroutePath.setMap(map); // 地図上に表示(3)バス路線上にバス停マーカーを表示
・データの busstopPolesに入っている lat, lng データを利用してMaps JavaScript APIで地図上にバス停マーカーを描画します。
// 経度と緯度を指定してマップ上の地点を表すオブジェクトを作成
const latLang = new google.maps.LatLng(lat, lng);
// 地図上にマーカーを配置
const busstopPoleMarker = new google.maps.Marker({
position: latLang,
map,
icon: {
fillColor: '#FF0000',
// 後方(進行方向とは逆)を向いた閉じた矢印の記号
path: google.maps.SymbolPath.BACKWARD_CLOSED_ARROW,
scale: 3,
strokeColor: '#FF0000',
},
});(4)バス路線上のバス停マーカーに吹き出しでバス停名を表示
・データの busstopPolesに入っている title データを利用してMaps JavaScript APIで地図上にバス停名を描画します。
// 地図上にポップアップするウィンドウを作成
(new google.maps.InfoWindow({
content: `<div>${title}</div>`, // 情報ウィンドウに表示するコンテンツ
shouldFocus: false, // キーボードフォーカスをウィンドウ内に移動させない
disableAutoPan: true, // 地図の表示範囲を自動的に調整する「自動パン」機能を無効
})).open({ // 作成したウィンドウを表示
anchor: busstopPoleMarker, // マーカーに紐づける
map, // 表示対象地図オブジェクト
});(5)バス運行情報を表示
・バス車両を表すアイコン「緑色の◎マーク」を表示しますが、処理がややこしいので段落を分けます。→ 3. – (5)
3. – (5) バス運行情報を表示
バス車両の移動を表示するために次のアニメーションを表示します。
- バス車両アイコンの位置をバックエンドから取得したバス運行情報の始点~終点の区間で移動させる
- バス車両アイコンの色(緑色)の透明度を始点から終点に向かうにつれて透明にする
- 30秒間このアニメーションを表示したあと、バックエンドから新しいバス運行情報を取得する
アニメーションは下記のイメージです

この表示をするために下記の処理をしています。
1.バスのリアルタイム情報を消去
・現在動いているアニメーションが有る場合、30秒のインターバルタイマーを停止し、アニメ画像を消去します。
2.バス運行情報を取得
・バックエンドの「GET buses」にリクエストを送ります。
バックエンドでは選択されたバス停を含むバス運行系統情報データを抽出し、標柱情報とマッピングしたオブジェクト配列をJSONデータにして返します。
返ってくるデータはこんな感じです。
[
{
"coordinates": [
{
"lng": 139.73748,
"lat": 35.70165
},
{
"lng": 139.7368,
"lat": 35.70129
},
{
"lng": 139.73531,
"lat": 35.70065
},
{
"lng": 139.73239,
"lat": 35.69964
}
]
}
]3.バス車両アイコン作成
・返ってきた経路データの始点にマーカーを表示します。
const busAnimationPolyline = new google.maps.Polyline({
path: bus.coordinates, // バス車両アイコンの移動範囲を表す経度・緯度配列
icons: [
{
icon: {
path: google.maps.SymbolPath.CIRCLE, // 円形状マーカー
scale: 8,
strokeColor: '#393',
strokeOpacity: 1.0 // アイコンの透明度 (1~0)
},
offset: '0%', // pathの始点からの距離 (0~100%)
},
],
strokeWeight: 0, // 線の太さ。0なので表示しない。
map: map, // 描画する地図オブジェクト
});
始点からの距離が0(offset:’0%’)なので始点に表示されます。
4.バス車両アイコンをアニメーション表示
・setInterval()を使用して、0.05秒ごとに表示しているマーカーのプロパティを変更していくことでアニメーションを実現しています。
変更されるプロパティは、視点からの距離(offset)と透明度(icon.strokeOpacity)です。
let counter = 0;
const animateBusInterval = setInterval(() => { // 0.05秒ごとに実行
counter = (counter + 1) % 100; // 0.05秒ごとに1加算。100になったら0から。
const icons = polyline.get('icons'); // マーカー要素を取得
icons[0].offset = counter + "%"; // 始点からの距離を設定
icons[0].icon.strokeOpacity = (100 - counter) / 100; // 透明度設定
polyline.set('icons', icons);
}, 50); // 0.05秒で1アニメーション
5.30秒おきにデータを更新するためにタイマーセット
・30秒ごとに1.の処理に戻ります。
4. 「交通状況を表示」チェックボックス
このチェックボックスは、交通状況レイヤーを地図に表示・非表示します。
この機能はバスとは関係ないMaps JavaScript APIの機能で、まぁオマケのようなものです。
Maps JavaScript APIの交通状況レイヤに地図オブジェクトを渡すと表示されます。
trafficLayer = new google.maps.TrafficLayer(); // 交通状況レイヤ
if ($('#display_traffic_jam_flag').is(':checked')) { // チェックON?
trafficLayer.setMap(map); // 表示
} else {
trafficLayer.setMap(null); // 非表示
}


次回
以上で大まかですがフロントエンドの処理を一通り追うことができました。
これでGoogle Cloudのアカウント作成、Maps JavaScript APIの利用、公共交通オープンデータセンター開発者サイトのユーザー登録とAPIの利用が出来るようになりました。
前回も書いたように、日経ソフトウエアの記事ではサーバーにNode.jsを利用しているので、実際レンタルサーバーで動かそうと思うと無理があります。
そこで、サーバー側の処理をPythonで作ってレンタルサーバーで動かせるようにしようと思います。



コメント