Rails 登録した住所をGoogle Mapで表示させる

#実現したい事
Railsで登録した住所を元にgoogle map上に位置を表示するまでの実装方法をまとめました。
以下のデモのように入力フォームに登録した住所を用いて詳細ページでGoogle Map上にその位置を表示させます。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582516/c7167f14-b327-e94e-484a-22666a880513.gif)




#実装までの流れ

実装の流れとしては以下の通りです。

**1.Google Mapの機能を使うためにAPIキーを取得する**

**2.住所(address)、緯度(latitude)、経度(longitude)を登録するためのカラムを用意する**

**3.住所を登録するためのフォームを作成**

**4.gemのgeocoderを用意する→これによって住所の情報を元に緯度、経度を割り出してくれます**

**5.viewにGoogle Mapを表示させるための記述をする**

--ここまでで住所を元にGoogle Mapの表示はひとまず出来ます。
以下の6,7は追加で自分がつまずいた事、気をつける事をまとめました。

**6.住所を登録できてるのにMapにきちんと表示されない時**

**7.Github等で管理する場合、取得したAPIキーをpushしないために**

それでは、流れに沿って進めていきます！

#1.Google Mapの機能を使うためにAPIキーを取得する
こちらに関してはこの記事内では省略します。以下の方の記事がとても分かりやすかったです。
手順通りに進めれば問題なくできると思います。
https://qiita.com/tiara/items/4a1c98418917a0e74cbb

#2.住所、緯度、経度を登録するためのカラムを用意
住所(address)、緯度(latitude)、経度(longitude)のカラムを追加します。
`db/migrate/...`マイグレーションファイルへの記述でカラムを追加します。

```
class AddMapInfoToShops < ActiveRecord::Migration[5.2]
  def change
    add_column :shops, :address, :string //すでにaddressカラムが登録されている場合は必要ありません
    add_column :shops, :latitude, :float
    add_column :shops, :longitude, :float
  end
end
```

緯度、経度はfloat型にします。floatとは浮動小数点数型です。
難しい名前ですがざっくり言えば小数を扱える型です。

記述したら`rails db:migrate`してください。



#3.住所を登録するためのフォームを作成
Mapに表示させたい住所(`:address`)を登録できるようにします。
カラムの`:address`に住所が登録できればどんな形での実装でも大丈夫です。
以下は一例です。

```ruby:new.html.erb
<%= form_for(@shop) do |f| %>
 ...
 <h4>住所</h4>
 <%= f.text_field :address, placeholder: "住所" %>
 ...
 <%= f.submit "登録する" %>
<% end %>
```
<img width="728" alt="スクリーンショット 2020-03-15 3.20.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582516/241bd7b1-a292-f04c-dd90-00a7bef4f1cd.png">



#4.gemのgeocoderを用意する
Map表示する際に、Google Mapでは緯度、経度から位置を取得します。
しかし、今の段階では`address`に住所は入っているものの緯度、経度が入力されていません。

住所は簡単に調べられても緯度、経度までユーザー側で登録するのは面倒です。そこでgemの`geocoder`を使用します。
`geocoder`を使用すると住所の情報を元に緯度、経度を割り出してくれます。

```ruby:Gemfile
  gem "geocoder"
```
記述したら`bundle install`してください。

次に、geocoderを使うために適用するモデルに以下の記述をします。
今回は店舗の位置情報を表示したいので`/app/models/shop.rb`に記述しました。

```ruby:shop.rb
class Shop < ApplicationRecord
  geocoded_by :address
  after_validation :geocode, if: :address_changed?
end
```

これで`:address`を登録した際に`geocoder`が緯度、経度のカラムにも自動的に値を入れてくれるようになります。
この記述がないと`address`を登録しても`latitude`(緯度),`longitude`(経度)が登録されなくなってしまう(nillになる)ので注意です。

#5.viewにGoogle Mapを表示させる記述をする

では、実際にMapを表示させたいviewファイルにMapを表示させるための記述をしていきます。

（JS,CSSどちらもhtmlファイル内でまとめて記述していますが、本来はそれぞれJavaScriptはjsファイル、styleの記述はcssファイルに記述した方がコードとしては望ましいです。）


```ruby:show.html.erb
...
#shopとなっている部分の記述は自身の実装に合わせて変えてください。

<script type="text/javascript">
  function initMap() {
#latitude,longitudeから位置を特定
    var test ={lat: <%= shop.latitude %>, lng: <%= shop.longitude %>};
    var map = new google.maps.Map(document.getElementById('map'), {
              zoom: 15, 
              center: test
              });
    var transitLayer = new google.maps.TransitLayer();
    transitLayer.setMap(map);

    var contentString = '住所：<%= shop.address %>';
    var infowindow = new google.maps.InfoWindow({
      content: contentString
    });

 #Map上の指定した位置にピンを挿して表示する
    var marker = new google.maps.Marker({
                  position:test,
                  map: map,
                  title: contentString
                 });

     marker.addListener('click', function() {
       infowindow.open(map, marker);
     });
  }
</script>

#以下の記述の中にあるYOUR_API_KEYには取得したご自身のAPIキーを記述してください
<script async defer
              src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=YOUR_API_KEY&callback=initMap">
</script>
#表示するmapのcssです。ご自身でカスタマイズしてください。高さが設定されていないと表示されないことがあります。
<style type="text/css">
  #map { height: 200px;
         width: 70%;}
</style>

#mapの表示
<div id="map"></div>
...
```

実際の表示画面↓　（上記コードの記述は以下の画面のMap表示部分のみのコードです）
<img width="1131" alt="スクリーンショット 2020-03-14 20.09.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582516/60790562-8a79-8f64-600e-6f5a444fd7ae.png">


これで登録した住所を元にGoogle Mapへの表示ができるようになりました。

ただし、上の画面で指定した住所は東京都渋谷区とざっくりしています。これが東京都渋谷区◯-◯◯-◯◯◯というように詳細な地点の表示となると上手くいかない場合があります。

自分はこれで詰まりました。どうやらデフォルトの`geocoder`のままではより詳細な住所から経度、緯度を持ってくることが出来ないようです。

以下では、この問題を含めた表示がうまくいかない場合に確認すべきことをまとめました。

#6.住所を登録できてるのにMapが表示されない時
まず手順5の最後に触れた自分も詰まった東京都渋谷区◯-◯◯-◯◯◯のような細かい位置の指定での検索方法です。
なぜこれで指定できないのかというと、`geocoder`の検索精度が原因です。
中にはデフォルト状態の`geocoder`でも細かく住所を指定して検索できる地域もあるのですが、場所によっては検索できなくなります。

ここでいう`geocoder`の検索精度とは、住所から緯度、経度の二つを検索する精度という意味です。つまり住所によってデフォルトの`geocoder`では緯度、経度が検索しきれない場所が出てくるということです。

ではどうすればいいか、それはより地図情報が豊富な情報を元に緯度、経度を検索できるようにします。
地図情報を豊富に持ってるものとは何か、それはGoogleです！
そのため`geocoder`でもGoogle Map APIの情報源を使えるように設定すれば解決します。

`config`フォルダ内に`geocoder.rb`ファイルを作成します。

```
$ bin/rails g geocoder:config
```
ターミナルで上のコマンドを行うことによって`config/initializers/geocoder.rb`ファイルができます。
<img width="339" alt="スクリーンショット 2020-03-15 4.07.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582516/bcb1fc99-33bf-a613-f761-0880d1307a0d.png">

作成されたファイルの中身を変更して`geocoder`でgoogle　mapのAPIを使って緯度、経度を検索できるようにします。

```ruby:geocoder.rb
Geocoder.configure(
  # Geocoding options
  # timeout: 3,                 # geocoding service timeout (secs)
   lookup: :google,         # name of geocoding service (symbol)
  # ip_lookup: :ipinfo_io,      # name of IP address geocoding service (symbol)
  # language: :en,              # ISO-639 language code
   use_https: true,           # use HTTPS for lookup requests? (if supported)
  # http_proxy: nil,            # HTTP proxy server (user:pass@host:port)
  # https_proxy: nil,           # HTTPS proxy server (user:pass@host:port)

#YOUR_API_KEYにはご自身のAPIキーを記述してください。
   api_key: YOUR_API_KEY,               # API key for geocoding service
  # cache: nil,                 # cache object (must respond to #[], #[]=, and #del)
  # cache_prefix: 'geocoder:',  # prefix (string) to use for all cache keys

  # Exceptions that should not be rescued by default
  # (if you want to implement custom error handling);
  # supports SocketError and Timeout::Error
  # always_raise: [],

  # Calculation options
  # units: :mi,                 # :km for kilometers or :mi for miles
  # distances: :linear          # :spherical or :linear
)
```

これでGoogle Map APIを用いて`geocoder`の精度をあげることができます。より詳細な場所の指定ができるようになりました。


**それでも上手くいかない場合は**

・必要なカラムがきちんと追加できているか？
  →スペルミス、緯度、経度がfloat型になっていない、`rails db:migrate`していない等を確認。

・`geocoder`はきちんと機能しているか？
→手順の４番をきちんと実行できているか、`bundle install`しているか？

・そもそも`address`に値が入力、取得できているか？
→コントローラーで`:address`カラムに値を入力、取得はきちんとできているか？、スペルミス等がないか？

#7.Github等でのAPIキーの管理
地図表示の実装が上手くいった。よしとりあえずここまでGithubにpushしよう！と考える時に、気をつけなければならないのが取得したAPIキーの管理です。

上手く地図表示できたそのコードをそのままpushしてしまうとコード内に記述した自分のAPIキーまでpushしてしまい、悪用されてしまうかもしれません。その為、キーを管理するために一工夫が必要となります。

その方法はgemの`dotenv-rails`を用いてGithubにpushするコマンドを打ってもpushされないファイルを作り、その中でAPIキーを管理するようにします。

まずは、gem `dotenv-rails`を導入します。

```ruby:Gemfile
gem "dotenv-rails"
```
記述したら忘れず`bundle install`してください。

次にpushされないファイルを作成します。
実装したいアプリケーションの直下に`.env`ファイルを作成します。`app`等と同じステージに作成してください。
<img width="344" alt="スクリーンショット 2020-03-14 23.14.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582516/23f5793d-d942-4d20-5e3c-7387cee329ef.png">

作成したら`.env`ファイルに隠したいAPIキーの中身を記述します。

```ruby:.env
#YOUR_API_KEYには自身の取得したAPIキーを入力してください
GOOGLE_MAP_API_KEY=YOUR_API_KEY
```

次にこの作成した`.env`ファイルをGithubにpushした時、pushされないファイルとして扱うようにしていきます。
そのために`gitignore`というファイルの中に記述をします。
<img width="325" alt="スクリーンショット 2020-03-14 23.23.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/582516/5545748a-7388-c5eb-6887-1074426c2732.png">

`gitignore`ファイルの最後の行に以下の記述をしてください。
これでpushした時に`.env`ファイルは文字通り無視(ignore)されてpushされるようになります。

```ruby:.gitignore
# Ignore master key for decrypting credentials and more.
/.env
```

これでpushしてもAPIキーはpushせずに済むようになりました。

最後に今までAPIキーを直接書き込んでいた全てのファイルでAPIキーの記述を`.env`ファイル内で定義した`GOOGLE_MAP_API_KEY`に書き換えて完了です。

例：この記事の`show.html.erb`、`geocoder.rb`ファイルそれぞれ書き換えた場合↓

```ruby:show.html.erb

#以下のコードの YOUR_API_KEY → <%= ENV['GOOGLE_MAP_API_KEY'] %> に変更されています。
<script async defer
              src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<%= ENV['GOOGLE_MAP_API_KEY'] %>&callback=initMap">
</script>
```

```ruby:geocoder.rb
Geocoder.configure(
  ...
  api_key: ENV['GOOGLE_MAP_API_KEY'],
  ...
)
```

###余談：APIキーを記述したファイルをうっかりGithubにpushしてしまったら …

実は自分はこの`.env`ファイルでキーを管理する前にAPIキーを記述したファイルをすっかり忘れてGithubへpushしてしまいました。
その後、すぐにgoogleからメールが届きました。さらにGithubからもメールが。内容はどちらも同じで「あなたのgoogle map APIキーがGithub等で公開されてしまっているよ」というものでした。

どうやらAPIキーのような情報を察知するプログラムが企業、個人レベルであちこち働いているようです。

こうなった場合、焦らず対応しましょう。以下の２つを行えば大丈夫です。

**１.Googleから届いたメールが対応策をいくつか示してくれるので、それに従います。**
自分はこの対応策にしたがって公開してしまったAPIキーを破棄し、新しいAPIキーを発行するという方法を取りました。

**2.上の１番の対応だけでも解決しますが念のためGithub上のAPIキーを上げてしまったコミットを取り消します。**
Githubへpushしたコミットの取り消し方は検索すれば様々出てくるのでここでは割愛します。

#参考にさせていただいた記事
Google Map API　登録、キーの取得
https://qiita.com/tiara/items/4a1c98418917a0e74cbb

mapを表示するためのviewの記述
https://qiita.com/enzen/items/9a919a75ebf0a34e7b91

geocoderの導入、準備
https://remonote.jp/rails-googlemap

geocoder　緯度、経度の位置を詳細に検索するために
https://qiita.com/roark/items/2fedc1ebac763e72d70b

APIキーの管理、Githubにキーをあげずに管理する
https://qiita.com/ryosuketter/items/ceb592dc6b23a20e51b5

今回、上記の記事を参考に実装しました。
本当に助かりました。ありがとうございました。

まだまだ理解の足りない部分もあるので何かありましたらご指摘いただけますと幸いです。

ここまでお読みいただきありがとうございます。少しでも参考になれたら嬉しいです。