【rails】google maps api　地図情報含んだ投稿をして表示させる方法

##はじめに
・Maps JavaScript API
・Geocoding API

上記のAPIを使用して個人アプリの制作で地図を含んだ投稿をして、表示させる処理を実装しました。
いろいろな記事を参考にさせていただきでき結構ハマったのでまとめておきます。

※地図以外の投稿機能はできているものとしてまとめています


追記
[【rails】GoogleMapsAPI 緯度、経度が保存できない時の記述](https://qiita.com/sho-17/items/52d1f8c45c96c1a65c7f)
自分は緯度、軽度がうまく取得できていなかったので、同じような方がいましたらこちらの記事も参考にしてください。



#実装内容・イメージ写真
１.ユーザーに地名もしくは住所を入力してもらう

2.詳細ページにてgooglemapにマーカーを落として表示させる



###投稿時
※フロント部分はほぼデフォルトのままですご了承ください。

<img width="778" alt="投稿.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637128/e68ecaaf-5ba8-0bb3-a513-e6b25cb966d4.png">

###詳細ページ
<img width="481" alt="投稿表示.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637128/8b4c159d-1de0-ab5f-10fa-13f9419c1cfb.png">


#Google API
googlemapを使用するときはAPIを取得しなければいけません。
下記リンクからAPIのKEYを取得してください。
[Google Maps Platform](https://cloud.google.com/maps-platform/?hl=ja&utm_source=google&utm_medium=cpc&utm_campaign=FY18-Q2-global-demandgen-paidsearchonnetworkhouseads-cs-maps_contactsal_saf&utm_content=text-ad-none-none-DEV_c-CRE_320617583952-ADGP_Hybrid+%7C+AW+SEM+%7C+BKWS+~+Google+Maps+Platform+EXA-KWID_43700039913979220-kwd-436368166750-userloc_1009311&utm_term=KW_google%20maps%20platform-ST_google+maps+platform&gclid=CjwKCAjwiMj2BRBFEiwAYfTbCsortFkcJrltrpBIAUf5uPSx_oJocfdSQ3PYfHrgbn-bQoBnWiqw1hoCPCEQAvD_BwE)
取得方法については今回は割愛します。


今回作成したアプリでは
・Maps JavaScript API
・Geocoding API
を使用しますので有効にしておいてください。

#データベース作成
まずデータベースを作成します。
既に作成済みの場合はカラムを追加してください。

##postテーブル
| Column | Type | Options |
|:--|:--|:--|
| title | string | null: false |
| text | text | null: false |

###Association

has_one :spot

##spotテーブル
| Column | Type | Options |
|:--|:--|:--|
| address | string | null: false |
| latitude | float | null: false |
| longitude | float | null: false |
| review_id | references | foreign_key: true, null: false |

###Association

belongs_to :post



#gemのインストール
```ruby:Gemfile
gem "gmaps4rails"
gem "geocoder"
gem "gon"
gem "dotenv-rails"
```
Gemfileに記述できたらbundle installをしてください。

上から
・GoogleMapを簡単に作成できるgem "gmaps4rails"
・地名から緯度経度に変換できるgem "geocoder"
・JSでcontrollerの変数を使えるようにするgem "gon"
・GoogleMapAPIのkeyを隠すためのgem "dotenv-rails"


#JS導入

###application.html.hamlを編集

```ruby:application.html.haml
!!!
%html
  %head
   .
   .
   .
    = include_gon
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  %body
    = yield
    %script{src: "https://maps.googleapis.com/maps/api/js?key=#{ENV["GOOGLE_MAP_KEY"]}&callback=initMap"}
    %script{src: "//cdn.rawgit.com/mahnunchik/markerclustererplus/master/dist/markerclusterer.min.js"}
    %script{src: "//cdn.rawgit.com/printercu/google-maps-utility-library-v3-read-only/master/infobox/src/infobox_packed.js", type:"text/javascript"}
```

%head内にはgem "gon"を使えるようにするための記述をします。

%body内にはJSを使うための記述をしています。%head内に記述する方法もあると思いますが今回は%body内に記述しました。

ENV["GOOGLE_MAP_KEY"]には.envファイルに隠したAPIKEYを入れています。

```:.env
GOOGLE_MAP_KEY = "取得したAPIKEYを記述してください"
```
.envファイルを作成して上記を記述します。

###underscore.jsを作成

app/assets/javascripts下にunderscore.jsを作成して下記リンク先のコードをコピペして貼り付けます。

[underscore.js](https://underscorejs.org/underscore-min.js)

###application.jsを編集

application.jsを編集します。

```:application.js
//= require underscore
//= require gmaps/google
```

#modelの編集

次に各modelを以下のように編集します。

```ruby:post.rb
class Post < ApplicationRecord
  has_one :spot, dependent: :destroy
  accepts_nested_attributes_for :spot
end
```

```ruby:spot.rb
class Spot < ApplicationRecord
  belongs_to :post
  
  geocoded_by :address
  after_validation :geocode
end
```

#viewの編集

投稿ページを作成します。googlemapの投稿、表示のぶぶの記述しています。

住所や場所の名前を入力するフォームを作成します。

```ruby:new.html.haml
= form_with(model: @post, local: true, multipart: true) do |f|
  .spot
    = f.fields_for :spot do |s|
      = s.label :address, "レビュー場所(Google Mapで検索)", class: 'spot__title'
      = s.text_field :address, placeholder: "スポットを入力", id: "address", class: 'spot__text'
    %input{onclick: "codeAddress()", type: "button", value: "検索する"}
    .map{id: "map", style: "height: 320px; width: 640px;"}
```

次に投稿された詳細ページのgooglemapの部分を記述します。

```ruby:show.html.haml
.show
  .show__address
    = @post.spot.address
  .show__maps{id: "show_map", style: "height: 320px; width: 400px;"}
```

#controllerの編集

controllerを編集します。

```ruby:post.controller
def new
  @post = Review.new
  @post.build_spot
end

def create
  @review = Review.new(review_params)
  if @post.save
    redirect_to root_path
  else
    redirect_to new_review_path
  end
end

def show
  @post = Review.find(params[:id])
  @lat = @review.spot.latitude
  @lng = @review.spot.longitude
  gon.lat = @lat
  gon.lng = @lng
end

private

def review_params
  params.require(:post).permit(:title, :text,spot_attributes: [:address])
end
```
newアクションの.buildメソッドではhas_oneの関係にあたるので
`@post.build_spot`
としています。

showアクションで記述している

`@lat = @review.spot.latitude
@lng = @review.spot.longitude
gon.lat = @lat
gon.lng = @lng`

では、controllerで定義した`@lat`と`@lng`の変数をJavaScriptでも扱えるように、それぞれ`gon.lat`と`gon.lng`に代入しています。

#JavaScriptの作成

次にJavaScriptファイルを作成していきます。

asset/javascripts/ 内に googlemap.js を作成します。

```ruby:googlemap.js
let map //変数の定義
let geocoder //変数の定義

function initMap(){ //コールバック関数
  geocoder = new google.maps.Geocoder() //GoogleMapsAPIジオコーディングサービスにアクセス
  if(document.getElementById('map')){ //'map'というidを取得できたら実行
    map = new google.maps.Map(document.getElementById('map'), { //'map'というidを取得してマップを表示
      center: {lat: 35.6594666, lng: 139.7005536}, //最初に表示する場所（今回は「渋谷スクランブル交差点」が初期値）
      zoom: 15, //拡大率（1〜21まで設定可能）
    });
  }else{ //'map'というidが無かった場合
    map = new google.maps.Map(document.getElementById('show_map'), { //'show_map'というidを取得してマップを表示
      center: {lat: gon.lat, lng: gon.lng}, //controllerで定義した変数を緯度・経度の値とする（値はDBに入っている）
      zoom: 15, //拡大率（1〜21まで設定可能）
    });

    marker = new google.maps.Marker({ //GoogleMapにマーカーを落とす
      position:  {lat: gon.lat, lng: gon.lng}, //マーカーを落とす位置を決める（値はDBに入っている）
      map: map //マーカーを落とすマップを指定
    });
  }
}

function codeAddress(){ //コールバック関数
  let inputAddress = document.getElementById('address').value; //'address'というidの値（value）を取得

  geocoder.geocode( { 'address': inputAddress}, function(results, status) { //ジオコードしたい住所を引数として渡す
    if (status == 'OK') {
      let lat = results[0].geometry.location.lat(); //ジオコードした結果の緯度
      let lng = results[0].geometry.location.lng(); //ジオコードした結果の経度
      let mark = {
          lat: lat, //緯度
          lng: lng  //経度
      };
      map.setCenter(results[0].geometry.location); //最も近い、判読可能な住所を取得したい場所の緯度・経度
      let marker = new google.maps.Marker({
          map: map, //マーカーを落とすマップを指定
          position: results[0].geometry.location //マーカーを落とす位置を決める
      });
    } else {
      alert('該当する結果がありませんでした');
    }
  });   
}
```
上記の記述についてはhttps://qiita.com/kanato4/items/f2f3f7accd880224616a
を参考にさせていただきました。

#終わりに
以上なります！
初めてgoogleAPIを使用したアプリケーションの作成でこの部分だけでかなりの時間を使ってしまったので後学者のためにもし参考になれば幸いです。