GoogleMapsAPI, GeocodingAPIを使って投稿内容から緯度経度情報を取得し地図を表示させる (and 地図から投稿を検索できるようにする)

実務未経験者です。
ポートフォリオに組み込んだGoogleMapsAPI関連について、作っておきながら面談で全く説明できなかったので、
頭を整理するために流れをまとめたものを書いてみます。

アプリ: ラーメン屋の写真や情報を友達と共有できるSNS
Ruby: 2.6.5
Rails: 5.2.0
前提: GoogleCloudPlatformでAPIキーを取得すること


# 投稿内容から緯度経度情報を保存
postモデルでbefore_save時 (投稿保存する時)にgeocodeメソッドを呼ぶ。
geocodeメソッドでは、バリデーションの後にGeocoding APIを使って
店名と最寄駅情報から緯度経度を取得、緯度経度カラムに保存させている。

shop_nameカラム: 店名情報
nearestカラム: 最寄駅情報 (とてもわかりにくい)
latitudeカラム: 緯度情報
longitudeカラム: 経度情報

```app/models/post.rb
class Post < ApplicationRecord
  ~
  ~
  before_save :geocode
  ~
  ~

  private
    def geocode
      uri = URI.escape(
        "https://maps.googleapis.com/maps/api/geocode/json?address="
        + self.shop_name 
        + " "
        + self.nearest
        + "&key=" + Rails.application.credentials.GCP[:API_KEY] # APIキーを入れてください
      )
      res = HTTP.get(uri).to_s
      response = JSON.parse(res)
      if response["status"] == "OK"
        self.latitude = response["results"][0]["geometry"]["location"]["lat"]
        self.longitude = response["results"][0]["geometry"]["location"]["lng"]
      else
        self.latitude = 1
        self.longitude = 1
      end
    end
end
```

geocordingAPIを使って緯度経度情報を取得するには、下記のURIを用います。

```
https://maps.googleapis.com/maps/api/geocode/json?address=緯度経度情報を調べたい住所など&key=APIキー
```

### 投稿詳細画面

```erb:app/views/posts/show.html.erb
・
・
<div class="post_map">
　<div id="map"></div> <!-- cssでwidth, heightを指定しないと地図が表示されません -->
</div>
・
・
<script>
var latLng;
var marker;
var infoWindow;
function initMap() {
  latLng = {lat: <%= @post.latitude %>, lng: <%= @post.longitude %>};
  map = new google.maps.Map(document.getElementById('map'), {
    center: latLng,
    zoom: 15,
    mapTypeControl: false,
    streetViewControl: false
  });
  marker = new google.maps.Marker({
    position: latLng,
    map: map
  });
  infoWindow = new google.maps.InfoWindow({
    content: "<a href='http://www.google.com/search?q=<%= @post.shop_name %> <%= @post.nearest %>' target='_blank' style='color: #00f;'><%= @post.shop_name %> を検索</a><br><br><a href='http://www.google.com/search?q=<%= @post.shop_name %> ラーメン&tbm=isch' target='_blank'>画像検索 by google</a>"
  });
  marker.addListener('click', function() {
    infoWindow.open(map, marker);
  });
}
initMap();
</script>
```

```erb:app/views/layouts/application.html.erb(レイアウトファイル)
<!DOCTYPE html>
<html>
  <head>
    <title>・・・・・・</title>
    <!-- APIキーを読み込んでいます -->
    <script src="https://maps.googleapis.com/maps/api/js?key=<%= Rails.application.credentials.GCP[:API_KEY] %>&callback=initMap" defer></script>
    ・
    ・
    ・
  </head>
  <body>
    ・
    ・
    <%= yield %>
    ・
  </body>
</html>
```

![スクリーンショット 2020-09-19 17.44.09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589400/461422f2-a6bc-86af-b61a-c13527c396fd.jpeg)




# 地図から投稿を検索できるようにする

![スクリーンショット 2020-09-20 19.51.29.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589400/691ea845-6346-8bcc-7794-85ee5964a927.jpeg)

form_with と radio_button で、どのユーザーの投稿を地図に表示させるか選択する
↓
mapsコントローラのmapアクションで、投稿内容を含む変数@postsを定義
(投稿内容は.to_jsonでjson形式に変換、respond_to doで返す)
↓
アクション名と同じmap.js.erbがRailsによって自動的に開かれ、@postsを受け取り、
地図とマーカー (投稿内容)をセット

```erb:app/views/maps/index.html.erb

<%= form_with url: map_request_path, method: :get do |f| %>
  <%= f.radio_button :posts, "all_user", checked: true %>全てのユーザーの投稿
  <%= f.radio_button :posts, "following", disabled: current_user.nil? %>自分とフォロー中のユーザーの投稿
  <%= f.radio_button :posts, "current_user", disabled: current_user.nil? %>自分の投稿
  <%= f.submit '投稿されたお店を表示', class: "btn btn-primary" %>
  <button type="button" class="btn btn-success current_position" onclick="getLocation()">
    地図を現在地周辺に切り替える
  </button>
<% end %>

<div id="map_index"></div> <!-- cssでwidth, heightを指定しないと表示されません -->
<script>
  var map
  function initMap(){
    map = new google.maps.Map(document.getElementById('map_index'), {
    center: {lat: 37.67229496806523, lng: 137.88838989062504}, // 地図の中心を指定
    zoom: 6, // 地図のズームを指定
    mapTypeControl: false,
    streetViewControl: false
    });
  }
  function getLocation(){ // 現在地周辺に地図を移動させる
    navigator.geolocation.getCurrentPosition(
      function(position) {
        var latitude = position.coords.latitude;
        var longitude = position.coords.longitude;
        var latlng = new google.maps.LatLng(latitude, longitude);
        map.setCenter(latlng);
        map.setZoom(12);
      }
    );
  }
  initMap();
</script>
```

```config/routes.rb
Rails.application.routes.draw do
  ~
  ~
  resources :maps, only: [:index]
  get '/map_request', to: 'maps#map', as: 'map_request'
end
```

```app/controllers/maps_controller.rb
class MapsController < ApplicationController
  def index
  end

  def map
    if params[:posts] == "all_user"
      @posts = Post.all.to_json.html_safe
    elsif params[:posts] == "current_user"
      @posts = current_user.posts.to_json.html_safe
    elsif params[:posts] == "following"
      @posts = current_user.feed.to_json.html_safe
    end
    respond_to do |format|
      format.js { @posts }
    end
  end
end
```

```js:app/views/maps/map.js.erb
var posts = <%= @posts %>;
var marker = [];
var infoWindow = [];
function initMap(){
  for (let i = 0; i < posts.length; i++) {
    marker[i] = new google.maps.Marker({
      position: {lat: parseFloat(posts[i].latitude), lng: parseFloat(posts[i].longitude)},
      map: map,
      animation: google.maps.Animation.DROP
    });
    infoWindow[i] = new google.maps.InfoWindow({
      content: posts[i].shop_name + "<br>" + "<a href='/posts/" + posts[i].id + "' target='_blank' style='color: #00f;'>このお店に関する投稿を表示</a>"
    });
    markerEvent(i);
  }
};
initMap();

function markerEvent(i) {
  marker[i].addListener('click', function() { // マーカーをクリックしたとき
    infoWindow[i].open(map, marker[i]); // 吹き出しの表示
  });
}
```

### こんな感じ
![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/589400/5a670e2e-1648-e01e-48c9-c81367ceb6f4.gif)


## 参考にさせていただきました
[Rails5でGoogleMapを表示してみるまで](https://qiita.com/tiara/items/4a1c98418917a0e74cbb)
[【Google MAP】名称から場所を検索・特定する](https://qiita.com/yoshi_yast/items/521c1f36306a180f45dd)