🐷

[Rails]Google map APIを使用する方法

2023/04/06に公開

このように、mapを入れていくよ!

mapAPIを使用する大枠

方法のおおまかな順序は以下のようになる.

  1. Google Cloud Platformにアカウントを作成し、APIキーを取得する。
  2. 「googlemaps」などのGemを使用して、Google Maps APIをRailsアプリケーションに統合する。
  3. 投稿ページに、Google Mapsのマップを表示するフィールドを追加する。
  4. 投稿された住所をGoogle Maps APIに渡し、緯度と経度を取得する。
  5. 取得した緯度と経度をデータベースに保存し、イベントのマップ上の位置を指定する。

google map APIを使用

Google Maps APIを使用することで、Google Mapの機能をウェブサイトやアプリに埋め込むことができるようになる!

しかし!これを利用するには、keyの取得が必須です。

google map API:keyの取得方法

  • Google Maps API を利用するには、API キーという許可証のようなものを取得する必要がある。
  • ではここのページから、始めていきます。

  • [プロジェクト選択] => [新しいプロジェクト]選択 => 任意のプロジェクト名の入力をして[作成]

  • 以下の画面へ遷移するので、 [APIとサービス]の中の、[ライブラリ]を選択

  • 選択すると以下のページへ遷移するので、"javascript map api"で検索。

  • 遷移したら、有効を押しましょう。

  • 国を入力して続行です。

    - その後、支払い情報のページへいくので入力します。

- その後遷移したら、APIkeyの操作部分から、編集を押しましょう。

  • 以下の画面になるので、認証情報作成、=> APIキーでクリック

  • 以下のように作成されます。

  • では再度、APIkeyを編集押します。

  • アプリケーション制限の選択でGeocodingとmap javascript apiの二つ選択

  • web siteの制限の中で使用するURL登録

  • アプリケーションの制限は websiteにして登録、完成!!!

作成してゆく!!!!

今回の追加カラム

今回付け足す必要があるカラムは以下の4つのカラムです。

  • event_type(enum:オンライン・オフライン)
    // 以下三つはgoogle map APIの使用で必須カラム
  • 住所: adress (データ型:string)
  • 緯度: lat (データ型:float)
  • 経度: lng (データ型:float)

郵便番号は使わないの??

  • Google Map APIを使用する際には、郵便番号のカラムは必須ではない!!
    住所から緯度と経度を取得することができるので、緯度と経度のカラムがあれば、マップ上にピンを立てることができる。
    (※ただし、住所や郵便番号などの情報をもとにして、より正確な緯度と経度を取得するためには、
    Google Maps Geocoding APIを使用することが必要になる場合がある。)
    いずれにしろ、郵便番号はなくてOK!!!
  • 郵便番号と住所の二つのカラムでsaveすることでメソッドの作成が余計に多くなるし、
    住所は変わるけどlat, lngは変わらないから、基本的にlat, lngで検索することがいい。
追加カラム設定

class CreateEvents < ActiveRecord::Migration[6.1]
  def change
    create_table :events do |t|
      t.integer :creator_id, null: false
      t.datetime :date, null: false
      t.string :event_name, null: false
      t.text :event_introduction, null: false
      t.text :url, null: false
         #追加項目:以下4つ
      t.integer :event_type, null: false 
      t.text :adress 
      t.float :latitude
      t.float :longitude

      t.timestamps
    end
  end
end

住所と郵便番号を入力するフォームを追加

入力フォーム:全て掲載するとこうなる!
<div data-turbolinks="false">
  <div class="event-form container">
    <div class="row justify-content-center">
      <div class="col-md-8">
        <h1 class="event-form__title">イベント作成ページ</h1>
        <%= form_with model: Event do |f| %>
          <%= render partial: 'public/events/form', locals: { f: f, isEdit: false } %>
        <% end %>
      </div>
    </div>
  </div>
  <div type="text/javascript">
    <script>
      $(document).ready(function() {
        // Get references to the radio buttons and input fields
        var onlineRadio = $('#event_online');
        var offlineRadio = $('#event_offline');
        var gmapDiv = $('#gmap');

        gmapDiv.hide();

        onlineRadio.change(function() {
          gmapDiv.hide();
        });

        offlineRadio.change(function() {
          gmapDiv.show()
        });
      });
    </script>
    <script>
      let map;
      let geocoder;
      let address;
      let latitude;
      let longitude;
      let markers = [];

      function addMarker(location) {
        // Remove any existing markers from the map
        removeMarkers();

        // Create a new marker at the given location and add it to the markers array
        let marker = new google.maps.Marker({
          position: location,
          map: map,
        });
        markers.push(marker);

        // Move the map camera to the new marker location
        map.setCenter(location);

        // Reverse geocode the marker location and display the current address in an HTML tag
        geocoder.geocode({ location: location }, function (results, status) {
          if (status === "OK") {
            if (results[0]) {
              let currentAddress = results[0].formatted_address;
              document.getElementById("address_field").innerHTML = currentAddress;
            }
          }
        });
      }

      function removeMarkers() {
        // Remove all markers from the map and clear the markers array
        for (let i = 0; i < markers.length; i++) {
          markers[i].setMap(null);
        }
        markers = [];
      }


      function codeAddress() {
        let inputAddress = document.getElementById("address").value;

        geocoder.geocode({ address: inputAddress }, function (results, status) {
          if (status == "OK") {
            // Add a marker at the input address and display its address and coordinates
            let currentAddress = results[0].formatted_address;
            let location = results[0].geometry.location;
            addMarker(location);

            address = currentAddress;
            latitude = location.lat();
            longitude = location.lng();

            // Display the latitude and longitude in HTML tags
            document.getElementById("address_field").value = currentAddress;
            document.getElementById("latitude_field").value = location.lat();
            document.getElementById("longitude_field").value = location.lng();
          } else {
            alert("Geocode was not successful for the following reason: " + status);
          }
        });
      }

       function initMap() {
        geocoder = new google.maps.Geocoder();
        navigator.geolocation.getCurrentPosition(function (position) {
          let latLng = {
            lat: position.coords.latitude,
            lng: position.coords.longitude,
          };
          map = new google.maps.Map(document.getElementById("map"), {
            zoom: 15,
            center: latLng,
          });

          // Add a marker at the user's location and add it to the markers array
          let marker = new google.maps.Marker({
            position: latLng,
            map: map,
            title: "Your current location",
          });
          markers.push(marker);

          // Reverse geocode the user's location and display the current address in an HTML tag
          geocoder.geocode({ location: latLng }, function (results, status) {
            if (status === "OK") {
              if (results[0]) {
                let currentAddress = results[0].formatted_address;

                address = currentAddress;
                latitude = position.coords.latitude;
                longitude = position.coords.longitude;

                // Display the latitude and longitude in HTML tags
                document.getElementById("address_field").value = currentAddress;
                document.getElementById("latitude_field").value = position.coords.latitude;
                document.getElementById("longitude_field").value = position.coords.longitude;
              }
            }
          });
        });
      };

      var script = document.createElement('script');
      script.src = "https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAPS_API_KEY'] %>&libraries=places&callback=initMap";
      script.async = true;

      // Attach your callback function to the `window` object
      window.initMap = initMap;
      // Append the 'script' element to 'head'
      document.head.appendChild(script);
    </script>
  </div>
</div>

上記で使用したform partial file

<div class="form-group form-group__name">
  <label for="event_name">
    イベント名
  </label>
  <%= f.text_field :event_name, required: true, class: "form-control" %>
</div>
<div class="form-group form-group__introduction">
  <label for="event_introduction">
    イベント紹介
  </label>
  <%= f.text_area :event_introduction, required: true, rows: 15, cols: 10, class: "form-control" %>
</div>
<div class="form-group form-group__type">
  <label for="event_type">
    イベントタイプ
  </label>
  <div class=radio-container>
    <div class="radio-wrapper">
      <%= f.radio_button :event_type, :online, checked: "checked", id: "event_online" %>
      <%= f.label :online, class: "event_type_label" %>
    </div>
    <div class="radio-wrapper">
      <%= f.radio_button :event_type, :offline, id: "event_offline" %>
      <%= f.label :offline, class: "event_type_label" %>
    </div>
  </div>
  <div id="gmap">
    <div class="form-group address-form">
      <!-- 地名入力用のinputを追加 -->
      <input id="address" class="form-control" type="textbox" value="">
      <!-- buttonをクリックしたらcodeAddressを実行 -->
      <button type="button" onclick="codeAddress()">住所検索</button>
    </div>
    <%= f.text_field :address, class: "form-control form-control__gmap", hidden: false, id: 'address_field', readonly: true %>
    <%= f.text_field :latitude, class: "form-control form-control__gmap", hidden: true, id: 'latitude_field', readonly: true %>
    <%= f.text_field :longitude, class: "form-control form-control__gmap", hidden: true, id: 'longitude_field', readonly: true %>
    <div id="map" style="width: 100%; height: 500px;"></div>
  </div>
</div>
<div class="form-group form-group__url">
  <label for="url">
    参加用URL(ex.zoom)
  </label>
  <%= f.text_field :url, required: true, rows: 25, cols: 10, class: "form-control" %>
</div>
<div class="form-group form-group_date">
  <label for="date">
    開催日時
  </label>
  <div>
    <%= f.datetime_field :date, required: true, min: Date.today,class: "form-control" %>
  </div>
</div>
<div class="form-group form-group__thumbnail">
  <label for="event_thumbnail">
    Thumbnail
  </label>
  <div>
    <%= f.file_field :event_image, accept: "image/*" %>
  </div>
</div>
<div class="form-group form-group__button-wrapper">
  <%= f.submit isEdit ? '更新する' : '投稿する', name: 'commit', class: "btn form-group__submit" %>
</div>

ちょっと記述は長いのだけど、ポイントを書いていきます。

  • <div data-turbolinks="false">
    この記述はjavascriptの記述を行う際は、ほぼ必須と言っても過言ではない!!!

RubyのERBファイル内でJavaScriptの記述がある場合、<div data-turbolinks="false">は必須ではありませんが、Turbolinksがページの再読み込みを抑制することによってJavaScriptの動作に影響を与える可能性があるため、推奨されている。

  • javascriptの記述に関して
最初のブロック
 <script>
      $(document).ready(function() {
        // Get references to the radio buttons and input fields
        var onlineRadio = $('#event_online');
        var offlineRadio = $('#event_offline');
        var gmapDiv = $('#gmap');

        gmapDiv.hide();

        onlineRadio.change(function() {
          gmapDiv.hide();
        });

        offlineRadio.change(function() {
          gmapDiv.show()
        });
      });
    </script>

最初のscriptブロックは、イベントのオンライン/オフラインステータスのラジオボタンが、オンラインにクリックされたときに、Googleマップを表示するためのdiv要素を非表示にする設定です。

後半のscriptブロック
    <script>
      let map;
      let geocoder;
      let address;
      let latitude;
      let longitude;
      let markers = [];

      function addMarker(location) {
        // Remove any existing markers from the map
        removeMarkers();

        // Create a new marker at the given location and add it to the markers array
        let marker = new google.maps.Marker({
          position: location,
          map: map,
        });
        markers.push(marker);

        // Move the map camera to the new marker location
        map.setCenter(location);

        // Reverse geocode the marker location and display the current address in an HTML tag
        geocoder.geocode({ location: location }, function (results, status) {
          if (status === "OK") {
            if (results[0]) {
              let currentAddress = results[0].formatted_address;
              document.getElementById("address_field").innerHTML = currentAddress;
            }
          }
        });
      }

      function removeMarkers() {
        // Remove all markers from the map and clear the markers array
        for (let i = 0; i < markers.length; i++) {
          markers[i].setMap(null);
        }
        markers = [];
      }


      function codeAddress() {
        let inputAddress = document.getElementById("address").value;

        geocoder.geocode({ address: inputAddress }, function (results, status) {
          if (status == "OK") {
            // Add a marker at the input address and display its address and coordinates
            let currentAddress = results[0].formatted_address;
            let location = results[0].geometry.location;
            addMarker(location);

            address = currentAddress;
            latitude = location.lat();
            longitude = location.lng();

            // Display the latitude and longitude in HTML tags
            document.getElementById("address_field").value = currentAddress;
            document.getElementById("latitude_field").value = location.lat();
            document.getElementById("longitude_field").value = location.lng();
          } else {
            alert("Geocode was not successful for the following reason: " + status);
          }
        });
      }

       function initMap() {
        geocoder = new google.maps.Geocoder();
        navigator.geolocation.getCurrentPosition(function (position) {
          let latLng = {
            lat: position.coords.latitude,
            lng: position.coords.longitude,
          };
          map = new google.maps.Map(document.getElementById("map"), {
            zoom: 15,
            center: latLng,
          });

          // Add a marker at the user's location and add it to the markers array
          let marker = new google.maps.Marker({
            position: latLng,
            map: map,
            title: "Your current location",
          });
          markers.push(marker);

          // Reverse geocode the user's location and display the current address in an HTML tag
          geocoder.geocode({ location: latLng }, function (results, status) {
            if (status === "OK") {
              if (results[0]) {
                let currentAddress = results[0].formatted_address;

                address = currentAddress;
                latitude = position.coords.latitude;
                longitude = position.coords.longitude;

                // Display the latitude and longitude in HTML tags
                document.getElementById("address_field").value = currentAddress;
                document.getElementById("latitude_field").value = position.coords.latitude;
                document.getElementById("longitude_field").value = position.coords.longitude;
              }
            }
          });
        });
      };

      var script = document.createElement('script');
      script.src = "https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAPS_API_KEY'] %>&libraries=places&callback=initMap";
      script.async = true;

      // Attach your callback function to the `window` object
      window.initMap = initMap;
      // Append the 'script' element to 'head'
      document.head.appendChild(script);
    </script>
  • initMap関数を定義しました。この関数は、ブラウザの現在の位置情報を取得し、その位置情報を中心にした地図を表示するもの。また、現在の位置にマーカーを追加して、マーカーの位置を逆ジオコーディングし、その住所をHTMLタグに表示します。

  • addMarker関数。この関数は、引数として渡された位置にマーカー(ピン)を追加。
    また、地図から既存のマーカーを削除し、新しいマーカーをマーカー配列に追加します。
    さらに、新しいマーカーの位置に移動し、その位置を逆ジオコーディングして、住所をHTMLタグに表示します。

  • removeMarkers関数。マーカー配列に格納されているすべてのマーカーを地図から削除し、マーカー配列を空にする。

  • codeAddress関数。入力された住所をジオコーディングして、その位置にマーカーを追加する。
    また、住所とその座標をHTMLタグに表示します。
    入力された住所がジオコーディングできない場合、アラートメッセージを表示する。

  • 最後に、Google Maps APIを読み込むためのスクリプトを生成です!!!。
    このスクリプトは、<script>タグを使用してHTMLページに追加され、initMap関数がwindowオブジェクトに割り当てられます。<%= ENV['GOOGLE_MAPS_API_KEY'] %>は、環境変数からGoogle Maps APIキーを取得するためのコードであり、Google Maps APIを使用するために必要。

map apiを導入したページ全てで記入必須!!!これがないと表示されない!!API keyに関して

<script>
:
const script = document.createElement('script');
script.src = "https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAPS_API_KEY'] %>&libraries=places&callback=initMap";
script.async = true;
:
</script>
  • HTMLタグの中だったら、以下のように、まとめてかけるが。。。
    <script async src="xxxxxxxxxx"></script>
    javascriptの中でHTMLを書くときは、今回のこのかたちになります。

- そしてここで、APIのkeyの格納をします。が、ここに直接書かずに、変数を一旦入れます。
script.src = "https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAPS_API_KEY'] %>&libraries=places&callback=initMap";
=> GOOGLE_MAPS_API_KEYという変数にしました。

そして、envファイルを作成しその中に定義しましょう。

このように作成し、その中にこのように記述

GOOGLE_MAPS_API_KEY= ここにAPIkey入力

作ったenvファイルは、必ずgitignoreに記述しましょう!


今日はここまで!!!

Discussion