Google Maps APIを用いて地図上に投稿と紐付いたマーカーを立てる

Railsを用いて場所と音楽を紐付けたアプリを開発しました。
その際にGoogle Maps APIを使ったのでメモ。

# Google Maps API
Google Maps APIは[こちら](https://cloud.google.com/maps-platform/?hl=ja)からAPI Keyを発行して準備完了です。

# 投稿画面
今回は[こちら](https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete)のオートコンプリートを使いました。
新規投稿画面で入力フォームに地名を入力→選択した場所にマーカーが立ってinfowindow表示→「OK」を押すとhidden_fieldからデータベースに保存、という流れです。

```place_select.js
      
      function initAutocomplete() {
        var map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 34.703209, lng: 135.497152},
          zoom: 13
        });
        var card = document.getElementById('pac-card');
        var input = document.getElementById('pac-input');
        var types = document.getElementById('type-selector');
        var strictBounds = document.getElementById('strict-bounds-selector');

        map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);

        var autocomplete = new google.maps.places.Autocomplete(input);

        // Bind the map's bounds (viewport) property to the autocomplete object,
        // so that the autocomplete requests use the current map bounds for the
        // bounds option in the request.
        autocomplete.bindTo('bounds', map);

        // Set the data fields to return when the user selects a place.
        autocomplete.setFields(
            ['geometry', 'icon', 'name']);

        var infowindow = new google.maps.InfoWindow();
        var infowindowContent = document.getElementById('infowindow-content');
        infowindow.setContent(infowindowContent);
        var marker = new google.maps.Marker({
          map: map,
          anchorPoint: new google.maps.Point(0, -29)
        });

        infowindowContent.style.display = "none";

        autocomplete.addListener('place_changed', function() {
          infowindow.close();
          marker.setVisible(false);
          var place = autocomplete.getPlace();
          if (!place.geometry) {
            // User entered the name of a Place that was not suggested and
            // pressed the Enter key, or the Place Details request failed.
            window.alert("No details available for input: '" + place.name + "'");
            return;
          }

          // If the place has a geometry, then present it on a map.
          if (place.geometry.viewport) {
            map.fitBounds(place.geometry.viewport);
          } else {
            map.setCenter(place.geometry.location);
            map.setZoom(17);  // Why 17? Because it looks good.
          }
          marker.setPosition(place.geometry.location);
          marker.setVisible(true);

          

          infowindowContent.children['place-icon'].src = place.icon;
          infowindowContent.children['place-name'].textContent = place.name;
          infowindow.open(map, marker);
          infowindowContent.style.display = "";
          document.getElementById('place-name_form').value = place.name;
          document.getElementById('latitude_form').value = place.geometry.location.lat();
          document.getElementById('longitude_form').value = place.geometry.location.lng();
        });

        // Sets a listener on a radio button to change the filter type on Places
        // Autocomplete.
        function setupClickListener(id, types) {
          var radioButton = document.getElementById(id);
          radioButton.addEventListener('click', function() {
            autocomplete.setTypes(types);
          });
        }

        setupClickListener('changetype-all', []);
        setupClickListener('changetype-address', ['address']);
        setupClickListener('changetype-establishment', ['establishment']);
        setupClickListener('changetype-geocode', ['geocode']);

        document.getElementById('use-strict-bounds')
            .addEventListener('click', function() {
              console.log('Checkbox clicked! New state=' + this.checked);
              autocomplete.setOptions({strictBounds: this.checked});
            });

  }
```

```ruby:new.html.erb
<% content_for :title, "新規投稿" %>
<div class="posts_content_wrapper">
<input id="pac-input" class="controls" type="text" placeholder="Search Box">
<div id="map"></div>
<div id="infowindow-content">
    <img src="" width="20" height="20" id="place-icon">
    <span id="place-name"  class="title" name="place_name"></span>
    <%= form_with(model:@post, url:create_post_path, local:true) do |form| %>
    <%= form.hidden_field(:place_name, id: "place-name_form", value:"") %>
    <%= form.hidden_field(:latitude, id: "latitude_form", value:"") %>
    <%= form.hidden_field(:longitude, id: "longitude_form", value:"") %>
    <%= form.hidden_field(:user_id, id: "user_id", value:"#{@current_user.id}") %>
    <%= form.hidden_field(:status, id: "status", value:"draft") %>
    <div>この場所を設定しますか？</div>
    <div class="button_wrapper">
    <input type="submit" value="OK" class="button_for_place">
    </div>
    <% end %>
</div>

<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAPS_API'] %>&libraries=places&callback=initAutocomplete" async defer></script>
</div>
</div>
```

こんな感じで選択した場所の名前と緯度経度をデータベースに保存します。

# ホーム画面
投稿一覧をホーム画面の地図上にマーカーとして立てて、マーカーをクリックするとinfowindowが出て、その場所に紐付けられた投稿の一覧ページに飛べるようにしました。

```javascript:
function initMap() {
        var map = new google.maps.Map(document.getElementById('map'), {
          center: {lat: 34.703209, lng: 135.497152},
          zoom: 13
        });
        
        var infowindow = new google.maps.InfoWindow();
        var infowindowContent = document.getElementById('infowindow-content-index');
        infowindow.setContent(infowindowContent);
        infowindowContent.style.display = "none";

        <% @posts.each do |post| %>
        <% @user = User.find_by(id: post.user_id) %>
        var marker_<%= post.id %> = new google.maps.Marker({
          position: {lat: <%= post.latitude %>, lng: <%= post.longitude %>},
          map: map,
          anchorPoint: new google.maps.Point(0, -29)
        });
        
        marker_<%= post.id %>.addListener('click', function() {
          infowindow.open(map, marker_<%= post.id %>);
          infowindowContent.style.display = "";
          document.getElementById('thumbnail_div').src = "<%= post.album_image %>";
          document.getElementById('song_info_title').textContent = "<%= post.song_title.html_safe %>";
          document.getElementById('song_info_artist').textContent = "<%= post.artist_name %>";
          document.getElementById('link_to_post').href = "/posts/<%= post.place_name %>"
        });
        <% end %>
      }
```

というわけでこれで地図上にマーカーが表示されます。
ただ、これだと同じ場所に紐付いた投稿が1つしか表示できないので何とかできないかなあと思ってます。。