Google Maps APIでお店のランキング表示をしてみると[PHP & GAS編](3/3)
GASでお店ランキング表示
今度は、サーバサイド・スクリプトの注目株「GAS」で、Google Maps APIを利用してお店ランキング表示を実装します。
「GAS」(Google Apps Script)は、その名の通り、Googleが提供するサーバサイド・スクリプトで、JavaScriptをベースに作られているので非常に取っ付きやすい言語です。
しかも、自分でレンタルサーバを借りなくても、Googleアカウントを持っていれば無料でGoogleサーバを利用できるので、お手軽に開発できるのがうれしいですね!
【サンプル2】GASでお店ランキング表示
/*
HTTP GETをハンドリング
*/
function doGet(e){
//検索場所
var address = e.parameter.address;
//検索キーワード
var keyword = e.parameter.keyword;
//検索範囲(メートル)
var radius = e.parameter.radius;
//お店情報取得
var resultHTML = getPlace(address, keyword, radius);
//HTMLを返却
var output = HtmlService.createHtmlOutput(resultHTML);
output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return output;
}
/*
お店情報取得
address:検索場所
keyword:検索キーワード
radius:検索範囲(メートル)
*/
function getPlace(address, keyword, radius) {
//Google Maps API の APIキー
var apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
//【Geocoding API】住所→緯度・経度
var geocodeApiUrl = "https://maps.googleapis.com/maps/api/geocode/json";
geocodeApiUrl += "?key=" + apiKey;
geocodeApiUrl += "&address=" + encodeURIComponent(address);
//Geocoding APIにリクエスト
var options = { muteHttpExceptions: true };
var geocodeJson = UrlFetchApp.fetch(geocodeApiUrl, options);
//JSON文字列をパースしてオブジェクトにする
var geocodeData = JSON.parse(geocodeJson.getContentText());
//緯度・経度の取得
var lat, lng;
if (geocodeData.status == "OK"){
lat = geocodeData.results[0].geometry.location.lat;
lng = geocodeData.results[0].geometry.location.lng;
} else if(geocodeData.status == "ZERO_RESULTS") {
return "【Geocoding API】検索結果が0件です。";
} else if(geocodeData.status == "ERROR") {
return "【Geocoding API】サーバ接続に失敗しました。";
} else if(geocodeData.status == "INVALID_REQUEST") {
return "【Geocoding API】リクエストが無効でした。";
} else if(geocodeData.status == "OVER_QUERY_LIMIT") {
return "【Geocoding API】リクエストの利用制限回数を超えました。";
} else if(geocodeData.status == "REQUEST_DENIED") {
return "【Geocoding API】サービスが使えない状態でした。";
} else if(geocodeData.status == "UNKNOWN_ERROR") {
return "【Geocoding API】原因不明のエラーが発生しました。";
}
//【Places API】検索エリアのお店情報取得
var placeApiUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";
placeApiUrl += "?key=" + apiKey;
placeApiUrl += "&location=" + lat + "," + lng;
placeApiUrl += "&radius=" + radius;
placeApiUrl += "&types=restaurant";
placeApiUrl += "&keyword=" + encodeURIComponent(keyword);
placeApiUrl += "&language=ja";
//Places APIにリクエスト
options = { muteHttpExceptions: true };
var placeJson = UrlFetchApp.fetch(placeApiUrl, options);
//JSON文字列をパースしてオブジェクトにする
var placeData = JSON.parse(placeJson.getContentText());
//お店情報取得
var placesList = new Array();
var nextPageToken = undefined;
if (placeData.status == "OK"){
//resultsをplacesList配列に結合
placesList = placesList.concat(placeData.results);
//next_page_tokenを取得
nextPageToken = placeData.next_page_token;
} else if(placeData.status == "ZERO_RESULTS") {
return "【Places API】検索結果が0件です。";
} else if(placeData.status == "ERROR") {
return "【Places API】サーバ接続に失敗しました。";
} else if(placeData.status == "INVALID_REQUEST") {
return "【Places API】リクエストが無効でした。";
} else if(placeData.status == "OVER_QUERY_LIMIT") {
return "【Places API】リクエストの利用制限回数を超えました。";
} else if(placeData.status == "REQUEST_DENIED") {
return "【Places API】サービスが使えない状態でした。";
} else if(placeData.status == "UNKNOWN_ERROR") {
return "【Places API】原因不明のエラーが発生しました。";
}
//next_page_tokenが取得された場合は次ページあり。
//next_page_tokenが取得できなくなるまで、
//次ページ情報の取得を繰り返す。
while (nextPageToken != undefined){
//2秒程間隔をおく(連続リクエストすると取得に失敗する)
Utilities.sleep(2000);
//【Places API】次ページのお店情報取得
placeApiUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";
placeApiUrl += "?key=" + apiKey;
placeApiUrl += "&pagetoken=" + nextPageToken;
//Place APIにリクエスト
options = { muteHttpExceptions: true };
placeJson = UrlFetchApp.fetch(placeApiUrl, options);
//JSON文字列をパースしてオブジェクトにする
placeData = JSON.parse(placeJson.getContentText());
if (placeData.status == "OK"){
//resultsをplacesList配列に結合
placesList = placesList.concat(placeData.results);
//next_page_tokenを取得
nextPageToken = placeData.next_page_token;
} else {
nextPageToken = undefined;
}
}
//ソートを正しく行うため、
//ratingが設定されていないものを
//一旦「-1」に変更する。
for (var i = 0; i < placesList.length; i++) {
if (placesList[i].rating == undefined){
placesList[i].rating = -1;
}
}
//ratingの降順でソート(連想配列ソート)
placesList.sort(function(a,b){
if(a.rating > b.rating) return -1;
if(a.rating < b.rating) return 1;
return 0;
});
//placesList配列をループして
//結果表示のHTMLタグを組み立てる
var resultHTML = "<ol>\n";
for (var i = 0; i < placesList.length; i++) {
var name = placesList[i].name;
var vicinity = placesList[i].vicinity;
var rating = placesList[i].rating;
//ratingが-1のものは「---」に表示変更
if (rating == -1) rating = "---";
//表示内容(評価+名称)
var content = "【" + rating + "】 " + name;
//詳細表示のリンク作成
resultHTML += "<li>\n";
resultHTML += "<a href=\"https://maps.google.co.jp/maps?q=" + encodeURIComponent(name + " " + vicinity) + "&z=15&iwloc=A\"";
resultHTML += " target=\"_blank\">" + content + "</a>\n";
resultHTML += "</li>\n";
}
resultHTML += "</ol>";
return resultHTML;
}
【編集注】
Places APIの結果が英語で取得されることが多くなったため、nearbysearchにパラメータ「language=ja」を追加しました。
ポイント解説
[1〜10行目]
/*
HTTP GETをハンドリング
*/
function doGet(e){
//検索場所
var address = e.parameter.address;
//検索キーワード
var keyword = e.parameter.keyword;
//検索範囲(メートル)
var radius = e.parameter.radius;
GETのHTTPリクエストを受け取ると、
doGetメソッドが呼び出されます。
「e.parameter」から、GETパラメータを取得します。
[30〜37行目]
//【Geocoding API】住所→緯度・経度
var geocodeApiUrl = "https://maps.googleapis.com/maps/api/geocode/json";
geocodeApiUrl += "?key=" + apiKey;
geocodeApiUrl += "&address=" + encodeURIComponent(address);
//Geocoding APIにリクエスト
var options = { muteHttpExceptions: true };
var geocodeJson = UrlFetchApp.fetch(geocodeApiUrl, options);
「Geocoding API」を呼び出して、住所(ランドマーク名)から緯度・経度を取得します。
UrlFetchApp.fetchでURLを呼び出すときは、
「muteHttpExceptions: true」を付けて、エラー発生時にもレスポンスが取得できるようにしてください。
[39〜40行目]
//JSON文字列をパースしてオブジェクトにする
var geocodeData = JSON.parse(geocodeJson.getContentText());
APIの返却結果のJSON文字列をパースして、GASで扱いやすようにJSONオブジェクトに変換します。
[42〜61行目]
//緯度・経度の取得
var lat, lng;
if (geocodeData.status == "OK"){
lat = geocodeData.results[0].geometry.location.lat;
lng = geocodeData.results[0].geometry.location.lng;
} else if(geocodeData.status == "ZERO_RESULTS") {
return "【Geocoding API】検索結果が0件です。";
} else if(geocodeData.status == "ERROR") {
return "【Geocoding API】サーバ接続に失敗しました。";
} else if(geocodeData.status == "INVALID_REQUEST") {
return "【Geocoding API】リクエストが無効でした。";
} else if(geocodeData.status == "OVER_QUERY_LIMIT") {
return "【Geocoding API】リクエストの利用制限回数を超えました。";
} else if(geocodeData.status == "REQUEST_DENIED") {
return "【Geocoding API】サービスが使えない状態でした。";
} else if(geocodeData.status == "UNKNOWN_ERROR") {
return "【Geocoding API】原因不明のエラーが発生しました。";
}
JSONデータのstatusが「OK」の場合は、結果が正常に受け取れています。
JSONオブジェクトを辿って、緯度・経度を取得しましょう。
statusが「OK」以外の時はエラーなので、エラーメッセージを返します。
UrlFetchApp.fetchで「muteHttpExceptions: true」を付けていない場合は、エラーが発生してエラーのレスポンスは取得できません。
[63〜77行目]
//【Places API】検索エリアのお店情報取得
var placeApiUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";
placeApiUrl += "?key=" + apiKey;
placeApiUrl += "&location=" + lat + "," + lng;
placeApiUrl += "&radius=" + radius;
placeApiUrl += "&types=restaurant";
placeApiUrl += "&keyword=" + encodeURIComponent(keyword);
placeApiUrl += "&language=ja";
//Places APIにリクエスト
options = { muteHttpExceptions: true };
var placeJson = UrlFetchApp.fetch(placeApiUrl, options);
「Places API」のnearbysearchを呼び出して、検索条件のお店情報を取得します。
keywordには全角文字が入る可能性があるので、
encodeURIComponentでURLエンコードをしておきましょう。
[103〜120行目]
//next_page_tokenが取得された場合は次ページあり。
//next_page_tokenが取得できなくなるまで、
//次ページ情報の取得を繰り返す。
while (nextPageToken != undefined){
//2秒程間隔をおく(連続リクエストすると取得に失敗する)
Utilities.sleep(2000);
//【Places API】次ページのお店情報取得
placeApiUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";
placeApiUrl += "?key=" + apiKey;
placeApiUrl += "&pagetoken=" + nextPageToken;
//Places APIにリクエスト
options = { muteHttpExceptions: true };
placeJson = UrlFetchApp.fetch(placeApiUrl, options);
//JSON文字列をパースしてオブジェクトにする
placeData = JSON.parse(placeJson.getContentText());
JSONデータの
next_page_tokenが取得できた場合は、次ページ情報があると判断できます。
nearbysearchの
「pagetoken」パラメータにnext_page_tokenを渡して、次ページ情報を取得しましょう。
next_page_tokenの値が取得できなくなるまで、次ページ情報の取得を繰り返します。
次ページ情報の取得は、連続リクエストすると上手く結果が得られませんので、
Utilities.sleepで2秒程待機してから行います。
[134〜148行目]
//ソートを正しく行うため、
//ratingが設定されていないものを
//一旦「-1」に変更する。
for (var i = 0; i < placesList.length; i++) {
if (placesList[i].rating == undefined){
placesList[i].rating = -1;
}
}
//ratingの降順でソート(連想配列ソート)
placesList.sort(function(a,b){
if(a.rating > b.rating) return -1;
if(a.rating < b.rating) return 1;
return 0;
});
ratingの降順でソートし、ランキング表示を実現します。
ソートの前に、ratingが設定されていないものを
undefinedかどうかで判定して、「-1」としておきましょう。
ソートでは、
sortで「連想配列ソート」を行います。
さて、ここまでの解説では、PHPのソースと同様のことを説明しています。
使うメソッドや記述が若干異なるだけで、ほぼほぼPHPソースからの焼き直しです。
「GAS」ならではの注意点は以下です。
[14〜17行目]
//HTMLを返却
var output = HtmlService.createHtmlOutput(resultHTML);
output.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
return output;
HTMLを返却する場合は、
HtmlService.createHtmlOutputメソッドを通して返却します。
この時、
setXFrameOptionsModeで
「HtmlService.XFrameOptionsMode.ALLOWALL」と指定することが重要。
この指定により、
クライアントサイドのiframeに結果を表示できるようになります。
完成したGASプログラムのURLをiframeに指定して、結果表示してみます。
「GAS」での実装も無事できました!
PHPと同様の結果が得られたと思います。
【まとめ】
サーバサイド・スクリプトで、Google Maps APIを使ったお店ランキング表示をしてみると・・・
自作したWebサービスで【LINE BOT】を作りたくなった!
次回予告。お店ランキング表示のLINEボットを作成します!!