Google Maps APIでお店のランキング表示をしてみると[PHP & GAS編](2/3)

PHPでお店ランキング表示

サーバサイド・スクリプトの王道「PHP」で、Google Maps APIを利用してお店ランキング表示を実装します。

【サンプル1】PHPでお店ランキング表示

<?php
//検索場所
$address = $_GET["address"];
//検索キーワード
$keyword = $_GET["keyword"];
//検索範囲(メートル)
$radius = $_GET["radius"];

//お店情報取得
$resultHTML = getPlace($address, $keyword, $radius);
//HTMLを返却
echo $resultHTML;

/*
 お店情報取得
  $address:検索場所
  $keyword:検索キーワード
  $radius:検索範囲(メートル)
*/
function getPlace($address, $keyword, $radius) {
  //Google Maps API の APIキー
  $apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
  
  //【Geocoding API】住所→緯度・経度
  $geocodeApiUrl = "https://maps.googleapis.com/maps/api/geocode/json";
  $geocodeApiUrl .= "?key=" . $apiKey;
  $geocodeApiUrl .= "&address=" . urlencode($address);
  
  //Geocoding APIにリクエスト
  $context = stream_context_create(array(
    'http' => array('ignore_errors' => true)
  ));
  $geocodeJson = file_get_contents($geocodeApiUrl, false, $context);
  
  //JSON文字列をデコードして連想配列にする
  $geocodeData = json_decode($geocodeJson, true);
  
  //緯度・経度の取得
  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】検索エリアのお店情報取得
  $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=" . urlencode($keyword);
  $placeApiUrl .= "&language=ja";
  
  //Places APIにリクエスト
  $context = stream_context_create(array(
    'http' => array('ignore_errors' => true)
  ));
  $placeJson = file_get_contents($placeApiUrl, false, $context);
  
  //JSON文字列をデコードして連想配列にする
  $placeData = json_decode($placeJson, true);
  
  //お店情報取得
  $placesList = array();
  $nextPageToken = null;
  if ($placeData["status"] == "OK"){
    
    //resultsをplacesList配列にマージ
    $placesList = array_merge($placesList, $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 (empty($nextPageToken) == false){
    //2秒程間隔をおく(連続リクエストすると取得に失敗する)
    sleep(2);
    
    //【Places API】次ページのお店情報取得
    $placeApiUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";
    $placeApiUrl .= "?key=" . $apiKey;
    $placeApiUrl .= "&pagetoken=" . $nextPageToken;
    
    //Places APIにリクエスト
    $context = stream_context_create(array(
      'http' => array('ignore_errors' => true)
    ));
    $placeJson = file_get_contents($placeApiUrl, false, $context);
    
    //JSON文字列をデコードして連想配列にする
    $placeData = json_decode($placeJson, true);
    
    if ($placeData["status"] == "OK"){
      
      //resultsをplacesList配列にマージ
      $placesList = array_merge($placesList, $placeData["results"]);
      //next_page_tokenを取得
      $nextPageToken = $placeData["next_page_token"];
      
    } else {
      $nextPageToken = null;
    }
  }
  
  //ソートを正しく行うため、
  //ratingが設定されていないものを
  //一旦「-1」に変更する。
  for ($i = 0; $i < count($placesList); $i++) {
    if (isset($placesList[$i]["rating"]) == false){
      $placesList[$i]["rating"] = -1;
    }
  }
  
  //ratingの降順でソート(多次元連想配列のソート)
  foreach ((array) $placesList as $key => $value) {
    $sort[$key] = $value['rating'];
  }
  array_multisort($sort, SORT_DESC, $placesList);
  
  //placesList配列をループして
  //結果表示のHTMLタグを組み立てる
  $resultHTML = "<ol>\n";
  
  for ($i = 0; $i < count($placesList); $i++) {
    $name = $placesList[$i]["name"];
    $vicinity = $placesList[$i]["vicinity"];
    $rating = $placesList[$i]["rating"];
    
    //ratingが-1のものは「---」に表示変更
    if ($rating == -1) $rating = "---";
    
    //表示内容(評価+名称)
    $content = "【" . $rating . "】 " . $name;
    
    //詳細表示のリンク作成
    $resultHTML .= "<li>\n";
    $resultHTML .= "<a href=\"https://maps.google.co.jp/maps?q=" . urlencode($name . " " . $vicinity) . "&z=15&iwloc=A\"";
    $resultHTML .= " target=\"_blank\">" . $content . "</a>\n";
    $resultHTML .= "</li>\n";
  }
  
  $resultHTML .= "</ol>";
  
  return $resultHTML;
}
?>
【編集注】
Places APIの結果が英語で取得されることが多くなったため、nearbysearchにパラメータ「language=ja」を追加しました。

ポイント解説

[2〜7行目]
//検索場所
$address = $_GET["address"];
//検索キーワード
$keyword = $_GET["keyword"];
//検索範囲(メートル)
$radius = $_GET["radius"];
「$_GET」でGETパラメータを取得します。

[24〜33行目]
//【Geocoding API】住所→緯度・経度
$geocodeApiUrl = "https://maps.googleapis.com/maps/api/geocode/json";
$geocodeApiUrl .= "?key=" . $apiKey;
$geocodeApiUrl .= "&address=" . urlencode($address);

//Geocoding APIにリクエスト
$context = stream_context_create(array(
  "http" => array("ignore_errors" => true)
));
$geocodeJson = file_get_contents($geocodeApiUrl, false, $context);
「Geocoding API」を呼び出して、住所(ランドマーク名)から緯度・経度を取得します。
file_get_contentsでURLを呼び出すときは、「"ignore_errors" = true」を付けて、エラー発生時にもレスポンスが取得できるようにしてください。

[35〜36行目]
//JSON文字列をデコードして連想配列にする
$geocodeData = json_decode($geocodeJson, true);
APIの返却結果のJSON文字列を、PHPで扱いやすように連想配列にデコードします。
json_decodeの第2引数を「false」にすると、オブジェクト型にデコードされますが、PHPでは連想配列のほうが扱いやすいと思います。

[38〜56行目]
//緯度・経度の取得
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」の場合は、結果が正常に受け取れています。
連想配列を辿って、緯度・経度を取得しましょう。

statusが「OK」以外の時はエラーなので、エラーメッセージを返します。
file_get_contentsで「"ignore_errors" = true」を付けていない場合は、Warning エラーが発生してエラーのレスポンスは取得できません。

[58〜71行目]
//【Places API】検索エリアのお店情報取得
$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=" . urlencode($keyword);
$placeApiUrl .= "&language=ja";

//Places APIにリクエスト
$context = stream_context_create(array(
  'http' => array('ignore_errors' => true)
));
$placeJson = file_get_contents($placeApiUrl, false, $context);
「Places API」のnearbysearchを呼び出して、検索条件のお店情報を取得します。
keywordには全角文字が入る可能性があるので、urlencodeでURLエンコードをしておきましょう。

[100〜116行目]
//next_page_tokenが取得された場合は次ページあり。
//next_page_tokenが取得できなくなるまで、
//次ページ情報の取得を繰り返す。
while (empty($nextPageToken) == false){
  //2秒程間隔をおく(連続リクエストすると取得に失敗する)
  sleep(2);
  
  //【Places API】次ページのお店情報取得
  $placeApiUrl = "https://maps.googleapis.com/maps/api/place/nearbysearch/json";
  $placeApiUrl .= "?key=" . $apiKey;
  $placeApiUrl .= "&pagetoken=" . $nextPageToken;
  
  //Places APIにリクエスト
  $context = stream_context_create(array(
    'http' => array('ignore_errors' => true)
  ));
  $placeJson = file_get_contents($placeApiUrl, false, $context);
JSONデータのnext_page_tokenが取得できた場合は、次ページ情報があると判断できます。
nearbysearchの「pagetoken」パラメータにnext_page_tokenを渡して、次ページ情報を取得しましょう。
next_page_tokenの値が取得できなくなるまで、次ページ情報の取得を繰り返します。

次ページ情報の取得は、連続リクエストすると上手く結果が得られませんので、sleepで2秒程待機してから行います。

[133〜146行目]
//ソートを正しく行うため、
//ratingが設定されていないものを
//一旦「-1」に変更する。
for ($i = 0; $i < count($placesList); $i++) {
  if (isset($placesList[$i]["rating"]) == false){
    $placesList[$i]["rating"] = -1;
  }
}

//ratingの降順でソート(多次元連想配列のソート)
foreach ((array) $placesList as $key => $value) {
  $sort[$key] = $value['rating'];
}
array_multisort($sort, SORT_DESC, $placesList);
ratingの降順でソートし、ランキング表示を実現します。
ソートの前に、ratingが設定されていないものをissetで判定して、「-1」としておきましょう。

ソートでは、array_multisortで「多次元連想配列のソート」を行います。

完成したPHPプログラムのURLをiframeに指定して、結果表示してみます。
検索場所:
KeyWord:
検索範囲:
★結果★
参考までに、iframeでPHPを呼び出すソースも載せておきます。(本題ではありませんが)
<table>
<tr>
  <td>検索場所:</td><td><input type="text" id="addressInput" value="横浜中華街" style="width: 200px"></td>
</tr>
<tr>
  <td>KeyWord:</td><td><input type="text" id="keywordInput" value="中華料理" style="width: 200px"></td>
</tr>
<tr>
  <td>検索範囲:</td>
  <td>
    <select id="radiusInput">
    <option value="200" selected>200 m</option>
    <option value="500">500 m</option>
    <option value="800">800 m</option>
    <option value="1000">1 km</option>
    <option value="1500">1.5 km</option>
    <option value="2000">2 km</option>
    <select>
  </td>
</tr>
<tr>
  <td colspan="2" style="padding: 10px">
    <input type="button" value="PHPでお店情報取得" onclick="getPlaces();">
  </td>
</tr>
</table>
★結果★<br />
<iframe id="results" style="width: 100%; height: 200px; border: 1px dotted; overflow-y: scroll; background: white;"></iframe>

<script>
function getPlaces(){
  var addressInput = document.getElementById("addressInput").value;
  var keywordInput = document.getElementById("keywordInput").value;
  var obj = document.getElementById("radiusInput");
  var radiusInput = Number(obj.options[obj.selectedIndex].value);
  
  var url = "https://www.delta-ss.com/labo/php/a016-sample.php";
  url += "?address=" + encodeURIComponent(addressInput);
  url += "&keyword=" + encodeURIComponent(keywordInput);
  url += "&radius=" + radiusInput;
  
  document.getElementById("results").src = url;
}
</script>
「PHP」での実装ができました。
次は、「GAS」(Google Apps Script)でお店ランキング表示を実装しますよ!

あなたへのおすすめ記事