GoogleMapsAPIを使用した開発1（地図の表示）

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

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

# 3.前提条件
APIキー取得済み
投稿機能を実装している

# 4.実装
### 使用gem
```ruby:Gemfile
gem 'geocoder'
gem 'gon'
```

### テーブル構造
```ruby:schema.rb
  create_table "posts", force: :cascade do |t|
    t.string "address"
    t.float "latitude"
    t.float "longitude"
  end
```

## 1.投稿された住所、地名から緯度経度を保存できるようにする
### 1.gem`geocoder`をインストール
```ruby:Gemfile
gem 'geocoder'
```
上記記載後`bundle install`

### 2.geocodingを行いたいカラム（今回は`address`）があるPostモデルに以下記述
```ruby:app/models/post.rb
class Post < ApplicationRecord
  # addressカラムの値（住所、地名など）からgeocoded_byが緯度経度を算出し保存してくれる
  geocoded_by :address
  # after_validation :geocodeでaddressカラムに変更があった場合に変更後の緯度経度を保存してくれる
  after_validation :geocode, if: :address_changed?
end
```
以上で投稿された住所や地名から緯度経度を算出し`latitudeカラム`と`longitudeカラム`に保存できるようになりました

## 2.Railsで定義した変数をJavascriptに渡せるようにする

### 1.gem`gon`をインストール
`gon`はRailsアプリ内でJavaScriptに変数を渡すことが出来るgem
```ruby:Gemfile
gem 'gon'
```
`bundle install`を忘れずに
### 2.Postモデルの投稿をデータベースから取得する
`gon.posts = @posts`と変数の頭に`gon`をつけることでjavascriptで使用できるようになります。
```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def show
    @posts = Post.all
    gon.posts = @posts
    # 
    @post = Post.find(params[:id])
    gon.post = @post
  end
```
以上でjavascriptにデータを渡す準備が整ったので次はAPIを使用してマップを表示させていきます。

## 3.投稿された緯度経度の情報を中心地点としてGooleMapを表示する
### 1.viewの記述
ここから`Maps JavaScript API`を使用してマップを表示させていきます
```ruby:view
      <div class="map">
        <div id="map_index"></div> # ここにマップが表示される
      </div>

      <script async src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API'] ?>&callback=initMap"></script>
```
- GOOGLE_MAP_APIの箇所は取得したkeyを直接記述するか、envファイルへ記述し環境変数で設定してください。

### 2.MAPを表示させる
```javascript
  //　変数定義
  let map;
  let geocoder;
  const spot = gon.post; // postsコントローラーから現在表示されている投稿のデータを格納している
  
  function initMap() {
    // Google MapsのGeocoderを初期化
    geocoder = new google.maps.Geocoder(); // ①
    map = new google.maps.Map(document.getElementById('map_index'), { // ②
      center: { // ③
        lat: spot.latitude,
        lng: spot.longitude
      },
      zoom: 16, // ④
    });
```
①では、Googleサーバーと通信するために必要なgoogle.maps.Geocoderのオブジェクト（インスタンス）を生成しています。
- APIには、このオブジェクトを通してアクセスします。

②地図オブジェクトの作成
- `google.maps.Map` : 地図オブジェクトを生成するコンストラクタです。
- `document.getElementById('map_index')`でHTML内の`map_index`要素を取得し、地図をその要素に描画します。

③地図の初期表示設定
- `center` : 地図の初期表示位置を指定します。`spot.latitude` と `spot.longitude` は、投稿データから取得した緯度と経度です。
- `const spot = gon.post;`で変数を定義しています。
- これにより、地図が表示されるときにこの座標が中心になります。

④`zoom: 16` : 地図の初期ズームレベルを設定します。ズームレベルが大きいほど詳細な地図が表示されます。

### 3.投稿されたすべての緯度経度からマップに表示させる
```javascript
    // 変数定義
    const spots = gon.posts; // postsコントローラーからスポットのデータを格納
    
    for (let i = 0; i < spots.length; i++) { // ①
      const markerLatLng = new google.maps.LatLng({ //　②
        lat: spots[i]['latitude'],
        lng: spots[i]['longitude']
      });
```
①`for (let i = 0; i < spots.length; i++)`は、spots 配列内の各要素に対して順番に処理を行うためのループです。

②google.maps.LatLngオブジェクトの作成
- `google.maps.LatLng`コンストラクタは、指定された緯度と経度を持つLatLngオブジェクトを作成します。
- spots[i]['latitude'] と spots[i]['longitude'] は、各スポットの緯度と経度を指定しています。

### 4. マーカーの表示
```javascript
      // 変数宣言
      let marker = [];
      
      marker[i] = new google.maps.Marker({ // ①
        position: markerLatLng, //　②
        map: map // ③
      });
```
①`new google.maps.Marker`で、新しいgoogle.maps.Markerオブジェクトを作成しています。
- このオブジェクトは、地図上に表示されるマーカーを表します。

②マーカーの位置を指定
- `position: markerLatLng`で、先程作成したgoogle.maps.LatLngオブジェクトmarkerLatLngを使用し、マーカーの位置を指定しています。

③マーカーを表示する地図オブジェクトを指定
- `map: map`で、マーカーを表示する対象の地図オブジェクトを指定しています。この地図オブジェクトは、前述のmap変数に格納されています。

### 5.吹き出し表示
```javascript
      // 変数宣言
      let infoWindow = null; // InfoWindowのインスタンスを格納
      const spots_location = []; // スポットの場所を格納する配列
      const spots_lat = []; // スポットの緯度を格納する配列
      const spots_lng = []; // スポットの経度を格納する配列
      
      // 吹き出しの表示
      let id = spots[i]['id']; // ①
      spots_location[i] = spots[i]['location']; // スポットの場所（location）を配列に保存
      spots_lat[i] = spots[i]['latitude']; // スポットの緯度を配列に保存
      spots_lng[i] = spots[i]['longitude']; // スポットの経度を配列に保存
      infoWindow = new google.maps.InfoWindow(); // 新しい情報ウィンドウを作成 // ②
```
①スポットの情報取得
- `let id = spots[i]['id'];`で、各スポットのIDを取得しています。
- `spots_location[i] = spots[i]['location'];`、`spots_lat[i] = spots[i]['latitude'];`、`spots_lng[i] = spots[i]['longitude'];` で、場所、緯度、経度を配列に保存しています。

②`infoWindow = new google.maps.InfoWindow();`で、新しいgoogle.maps.InfoWindowオブジェクトを作成しています。
- これは吹き出しの内容を表示するためのものです。

### 6.吹き出しをクリックしたときの処理を追加
```javascript
      google.maps.event.addListener(marker[i], 'click', function () { // ①
        infoWindow.setContent( // ②
          `<a href='/posts/${id}'>${spots[i]['location']}</a>`); // マーカーに対応する情報ウィンドウを地図上に表示
        infoWindow.open(map, marker[i]);
      });
    }
```
①マーカーのクリックイベントの設定
- `google.maps.event.addListener(marker[i], 'click', function () { ... }`で、各マーカーがクリックされたときの処理を設定しています。

②情報ウィンドウの内容設定
- `infoWindow.setContent(...)`で、情報ウィンドウの内容を設定しています。
- `<a href='/posts/${id}'>${spots[i]['location']}</a>` は、スポットの場所をリンクとして表示しています。リンクのURLには対応するスポットのIDが含まれています。
- `infoWindow.open(map, marker[i]);`で、対応するマーカーに紐づく情報ウィンドウを地図上に表示します。

# 5.終わりに
ここまでで、地図の表示まで実装できました。
間違っている点などあればご連絡いただけると助かります。