GoogleMapsAPIを使用した開発3（周辺情報取得機能）

# 1.はじめに
投稿された情報（緯度経度）からGooleMapsAPIを使用して地図を表示、地点間のルート検索機能、周辺情報の取得を実装したので知識の定着、復習のために記事を書きます。
今回はその３回目で周辺情報取得機能を実装していきます。

# 2.環境
Ruby (2.6.4)
Rails (6.1.7)
Maps JavaScript API	

# 3.前提条件
APIキー取得済み
投稿機能を実装している
下記を実装している
[GoogleMapsAPIを使用した開発1（地図の表示）](https://qiita.com/uuuDi_97722/items/6a064cf7e40d91a517c0)
[GoogleMapsAPIを使用した開発2(現在地取得)](https://qiita.com/uuuDi_97722/items/7acf76187459ee627421)

# 4.実装

## 1.PlacesAPIを有効にする
- 下記公式サイトを参考に導入してください。
[Places API公式](https://developers.google.com/maps/documentation/javascript/places?hl=ja)

## 2.initMap関数とgetCurrentLocationAndAddMarker関数にsearchPlaces関数を呼び出す記述を追加する
```javascript:initMap関数
    // 周辺のカフェを検索
    searchPlaces({
      lat: spot.latitude,
      lng: spot.longitude
    });
```
- `searchPlaces({ lat: spot.latitude, lng: spot.longitude });`で、searchPlaces 関数を呼び出しています。
引数として、表示されている投稿の緯度、経度を渡しています。

```javascript:getCurrentLocationAndAddMarker関数
     // 周辺のカフェを検索
     searchPlaces(currentLatLng);
```
- `searchPlaces(currentLatLng);`でsearchPlaces関数を呼び出しています。
- 引数として、現在地の緯度、経度を渡しています。

## 3.searchPlaces関数を使用して周辺のカフェを検索
```javascript
  変数宣言
  let service;

  function searchPlaces(location) {
    service = new google.maps.places.PlacesService(map); // ①

    if (service) { // ②
      const request = {
        location: location,
        radius: 300,
        types: ['cafe']
      };

      service.nearbySearch(request, function (results, status) { // ④
        if (status === google.maps.places.PlacesServiceStatus.OK) { // ⑤
          for (let i = 0; i < results.length; i++) { // ⑥
            createMarker(results[i]);
          }
        }
      });
    } else { // ⑦
      console.error('PlacesService is not available.');
    }
  }
```
①`service = new google.maps.places.PlacesService(map);`でPlacesAPIのPlacesServiceインスタンスを新しく作成します。
- このサービスを使用して、場所の検索を行います。
- 引数は、`new google.maps.Map`で取得したMapインスタンスです。

②`if (service) { ... }`でPlacesService が正常に作成されたかどうかを確認します。

③`const request = { ... };`で検索のためのリクエストオブジェクトを作成します。
- 指定された位置周辺の半径300メートル以内のカフェを検索するように設定しています。

④`service.nearbySearch(request, function (results, status) { ... });`ではnearbySearch メソッドを使用して、検索リクエストをPlaces APIに送信します。
- 結果が戻ると、指定されたコールバック関数が実行されます。
- `request`は検索のためのパラメータが設定されたオブジェクトです。location（検索の基準となる位置情報）、radius（検索半径）、types（検索する施設の種類）などが含まれています。
- `function (results, status) { ... }:`は、検索が完了した後に呼び出されるコールバック関数です。この関数には、検索結果と検索のステータスが渡されます。resultsには検索結果が含まれ、statusには検索のステータスが含まれます。
- 具体的な検索結果やステータスはこのコールバック関数内で処理されます。statusが`google.maps.places.PlacesServiceStatus.OK`の場合には検索が成功し、resultsに検索結果が含まれることになります。その後、resultsを元に処理を行います。

⑤`if (status === google.maps.places.PlacesServiceStatus.OK) { ... }`は検索が成功した場合の条件分岐です。

⑥`for (let i = 0; i < results.length; i++) { ... }`では検索結果の数だけ繰り返し処理を行い、各検索結果に対して createMarker 関数を呼び出してマーカーを作成します。
- resultsは検索結果の配列で、各要素は周辺の施設を表しています。
- forループはresults内の各施設に対して繰り返し処理を行います。
- createMarker(results[i])は、createMarker関数を呼び出し、引数として現在の施設を渡します。
- createMarker関数内で、引数として渡された施設情報を元に、新しいGoogle マップのマーカーを作成し、そのマーカーを地図上に表示します。

⑦`console.error('PlacesService is not available.');`で、PlacesServiceが利用できない場合にコンソールにエラーメッセージを表示させています。

## 4.PlacesAPIの検索結果に基づいてマーカーを作成
```javascript
  function createMarker(place) {
    const placeLoc = place.geometry.location; // ①

    const customIconUrl = '<%= asset_path("cafe32.png") %>'; // ②
    const marker = new google.maps.Marker({ // ③
      map: map,
      position: placeLoc,
      icon: customIconUrl
    });
```
①`const placeLoc = place.geometry.location;`で引数として渡されたplaceオブジェクトから、場所のジオメトリ情報を取得します。
- `geometry.location`には場所の緯度と経度が含まれています。

②`const customIconUrl = '<%= asset_path("cafe32.png") %>';`でカスタムアイコンの URL を指定します。
- Railsのasset_pathメソッドを使用して、アセットのパスを取得しています。

③`const marker = new google.maps.Marker({ ... });`で新しいマーカーオブジェクトを作成します。
以下のプロパティを設定します。
- map: map: マーカーを表示する地図オブジェクト。
- position: placeLoc: マーカーの位置を指定するための緯度と経度のオブジェクト。
- icon: customIconUrl: マーカーに使用するカスタムアイコンのURL。

# 5.PlacesAPIの詳細情報を取得
```javascript
    google.maps.event.addListener(marker, 'click', function () { // ①
      const request = { // ②
        placeId: place.place_id,
        fields: ['name', 'formatted_address', 'formatted_phone_number', 'website', 'rating', 'opening_hours',
          'photos'
        ]
      };
```
①`google.maps.event.addListener(marker, 'click', function () { ... });`でマーカーがクリックされたときのイベントリスナーを設定します。
- マーカーがクリックされると、指定された関数が実行されます。

②`const request = { ... };`でPlacesAPIの詳細情報を取得するためのリクエストオブジェクトを定義します。
- placeId プロパティには、取得したい場所の一意の識別子である place_id が指定されます。
- fields プロパティには、取得したい情報のフィールドが指定されます。
- 今回は、name（名前）、formatted_address（住所）、formatted_phone_number（電話番号）、website（ウェブサイト）、rating、opening_hours（営業時間）、photos（写真） の情報を取得するように指定されています。

# 6.詳細情報を含む情報ウィンドウを作成
```javascript
      service.getDetails(request, function (placeDetails, status) { // ①
        if (status === google.maps.places.PlacesServiceStatus.OK) { // ②
          infoWindow.setContent(formatPlaceDetails(placeDetails, placeLoc)); // ③
          infoWindow.open(map, marker); // ④
        }
      });
    });
  }
```
①`service.getDetails(request, function (placeDetails, status) { ... });`でservice オブジェクトの getDetails メソッドを呼び出します。
- このメソッドは、詳細情報を取得するためのリクエストを行います。request パラメータには、どの場所の詳細情報を取得するかと取得する情報の種類が含まれています。
- `request` 施設の詳細情報を取得するためのリクエストパラメータが含まれたオブジェクトです。このオブジェクトには、placeId（施設の一意の識別子）や取得したい情報のfields（取得するフィールド）などが含まれます。
- `function (placeDetails, status) { ... }`は、リクエストの結果を処理するためのコールバック関数です。
- `placeDetails`には詳細情報が含まれ、statusにはリクエストのステータスが含まれます。

②`if (status === google.maps.places.PlacesServiceStatus.OK) { ... }`でリクエストのステータスが成功した場合に処理を実行します。
- google.maps.places.PlacesServiceStatus.OK は、リクエストが成功したことを示す定数です。

③`infoWindow.setContent(formatPlaceDetails(placeDetails, placeLoc));`で取得した詳細情報を含む情報ウィンドウのコンテンツを設定します。
- formatPlaceDetails関数には、詳細情報と場所のジオメトリ情報が渡され、それを適切な形式で整形する処理が含まれています。

④`infoWindow.open(map, marker);`で地図上に情報ウィンドウを表示します。
- map パラメータには、情報ウィンドウを表示する地図オブジェクトが指定されており、markerパラメータには、情報ウィンドウを表示するマーカーオブジェクトが指定されています。

# 7.PlaceDetailsを整形する関数の設定
```javascript
  function formatPlaceDetails(placeDetails, placeLoc) {
    let content = `<strong>${placeDetails.name}</strong><br>`; // ①
    content += `住所: ${placeDetails.formatted_address}<br>`; // ②
    content += `電話番号: ${placeDetails.formatted_phone_number}<br>`; // ③

    if (placeDetails.website) { // ④
      content += `ウェブサイト: <a href="${placeDetails.website}" target="_blank">${placeDetails.website}</a><br>`;
    }

    if (placeDetails.rating) { // ⑤
      content += `評価: ${placeDetails.rating}/5<br>`;
    }

    if (placeDetails.opening_hours) { // ⑥
      content += '営業時間:<br>';
      content += '<ul>';
      placeDetails.opening_hours.weekday_text.forEach((weekdayText) => {
        content += `<li>${weekdayText}</li>`;
      });
      content += '</ul>';
    }

    // 写真が存在する場合のみ処理
    if (placeDetails.photos && placeDetails.photos.length > 0) { // ⑦
      content += '<br>写真:<br>';
      content += '<div>';

      // １枚目の画像だけを取得
      const firstPhoto = placeDetails.photos[0];
      const imageUrl = firstPhoto.getUrl({ // ⑧
        maxWidth: 200,
        maxHeight: 200
      });
      content += `<img src="${imageUrl}" alt="Place Photo"><br>`; // ⑨

      content += '</div>';
    }

    return content; // ⑩
  }
```
①`let content = <strong>${placeDetails.name}</strong><br>;`
②`content += 住所: ${placeDetails.formatted_address}<br>;`
③`content += 電話番号: ${placeDetails.formatted_phone_number}<br>;`
- それぞれ場所、住所、電話番号を表示するためのHTML文字列を生成します。

④`if (placeDetails.website) { ... }`
- ウェブサイトが存在する場合、それを表示するための HTML 文字列を追加します。${placeDetails.website} は場所のウェブサイトを示しています。

⑤`if (placeDetails.rating) { ... }`
- 評価が存在する場合、それを表示するための HTML 文字列を追加します。${placeDetails.rating} は場所の評価を示しています。

⑥`if (placeDetails.opening_hours) { ... }`
- 営業時間が存在する場合、それを表示するための HTML 文字列を追加します。${placeDetails.opening_hours.weekday_text} は場所の営業時間を示しています。

⑦`if (placeDetails.photos && placeDetails.photos.length > 0) { ... }`
- 写真が存在する場合、それを表示するためのHTML文字列を追加します。
- ${placeDetails.photos} は場所の写真のリストを示しています。

⑧`const imageUrl = firstPhoto.getUrl({ maxWidth: 200, maxHeight: 200 });`
最初の写真のURLを取得します。maxWidth、maxHeightパラメータは、表示する画像の最大の幅と高さを指定しています。

⑨`content += <img src="${imageUrl}" alt="Place Photo"><br>;`
- 画像を表示するための HTML 文字列を追加します。
- ${imageUrl} は画像のURLを示しています。

⑩`return content;`
- 最終的な HTML 文字列を返します。

# 5.おわりに
以上で周辺情報取得機能まで実装できました。
ご指摘があればご連絡いただけると幸いです。