👌

Rails|アプリにGoogleMap APIを導入する

2023/09/11に公開

目標


日本語学校の住所を登録したら、学校情報詳細画面でGoogle Mapが表示されるようになる。

開発環境

ruby 3.1.2p20
Rails 6.1.7.4
Cloud9

前提

Schoolモデルは作成済み。
Schoolモデルには、prefectureカラム、addressカラム、buildingカラムを作成済み。

API Keyを取得

1️⃣ Google Map Platformにアクセスし、プロジェクトを生成する。
https://mapsplatform.google.com/

2️⃣ 請求情報を登録する

3️⃣ 「Maps JavaScript API」と「Geocoding API」を有効化する

4️⃣ 「認証情報」より、APIキーを作成する。

5️⃣ 「認証情報」より、キーの制限を設定する。

詳細はこちらの記事を参照
https://qiita.com/nagase_toya/items/e49977efb686ed05eadb

緯度・経度カラムを追加する

Schoolモデルに緯度・経度カラムを追加する。

マイグレーションファイルの作成。

ターミナル
$ rails g migration add_column_to_schools

latitude(緯度)、longitude(経度)カラムを追加する。

xxxx_add_column_to_schools.rb
class AddColumnToSchool < ActiveRecord::Migration[6.1]
  def change
    add_column :schools, :latitude, :float
    add_column :schools, :longitude, :float
  end
end

DBへ反映。

ターミナル
$ rails db:migrate

住所登録用ビューの作成

<%= form_with model: School.new, url: admin_schools_path, method: :post do |f| %>
...
        <%= f.label :都道府県 %><span class="text-danger">*</span>
        <%= f.select :prefecture, School.prefectures.keys.map { |key| [I18n.t("enums.school.prefecture.#{key}"), key] }, {} %>

        <%= f.label :都道府県以降の住所 %><span class="text-danger">*</span>
        <%= f.text_area :address, class: "form-control", rows: "1", placeholder: "渋谷区1-2-3" %>

        <%= f.label :建物名 %>
        <%= f.text_field :building, class: "form-control", placeholder: "東京ビル1F" %>
...
<% end %>

今回、都道府県はプルダウンで選択、都道府県以降の住所は手入力にしていたので、上記のようなフォームになった。

また、住所欄に建物名まで入れると緯度経度が正確に計測できないようなので、建物名は別カラムに保存した。

gemの追加・設定

以下のライブラリをGemfileに追加。

Gemfile
gem 'geocoder'
gem 'gmaps4rails'

geocoder
住所情報を緯度経度の数値に変換するgem。

gmaps4rails
緯度経度の情報からマップ上にピンを指すgem。

ターミナル
$ bundle install

Schoolモデルに設定を追記。

schools.rb
class School < ApplicationRecord
  geocoded_by :full_address
  after_validation :geocode
  
  def full_address
    [prefecture, address].compact.join(' ')
  end

end

geocoded_by: full_address
geocoderfull_addressの住所を参照して、緯度経度を計算するように設定。

after_validation :geocode
Schoolモデルのバリデーションチェックが成功した後に、緯度経度を計算するように設定。

[prefecture, address].compact.join(' ')
Schoolモデルでは都道府県とそれ以降の住所を分けて保存しているので、それらを結合させて、full_addressとして取得できるようにしている。

.compact
配列からnilを取り除くメソッド。

Map表示部分のビューの作成

_map.html.erb
<div class="map">
  <div id="map" data-latitude="<%= school.latitude %>" data-longitude="<%= school.longitude %>">
  </div>
</div>

<script type="text/javascript">
  function initMap() {
    var lat = parseFloat(document.getElementById('map').dataset.latitude);
    var lng = parseFloat(document.getElementById('map').dataset.longitude);

    let map = new google.maps.Map(document.getElementById('map'), {
      center: { lat: lat, lng: lng },
      zoom: 15
    });

    var marker = new google.maps.Marker({
      position: {lat: lat, lng: lng},
      map: map
    });
  }
</script>

<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['API_KEY'] %>&callback=initMap" async defer></script>

<div id="map" data-latitude="<%= school.latitude %>" data-longitude="<%= school.longitude %>">
実際にMapが表示される部分。

function initMap() {
Google Mapを初期化するための関数。

var lat = parseFloat(document.getElementById('map').dataset.latitude);
IdがmapのHTML要素を取得し、そこで埋め込まれているdata-latitudeの値を取得している。また、取得した文字列を数値に変換する。

let map = new google.maps.Map(document.getElementById('map')...
新しいGoogle Mapを生成。また、Mapの中心位置とズームレベルを設定している。

var marker = new google.maps.Marker({...
マップ上にピンを立てている。ピンの位置は学校の緯度と経度で指定されている。

<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['API_KEY'] %>&callback=initMap" async defer></script>
Google Map PIAを読み込んでいる。

以上で、基本的な設定は完了。
以上の設定で、「千葉県千葉市」等のおおまかな住所であれば地図が生成できるが、
詳しい住所を入力すると、地図が生成されない場合がある。
そのような場合は、以下の手順を踏む。

詳しい住所を表示させる

geocoderの設定ファイルを作成する。

ターミナル
$ bin/rails g geocoder:config

以下の通り編集する。

config/initializers/geocoder.rb
Geocoder.configure(
  # Geocoding options
  # timeout: 3,                 # geocoding service timeout (secs)
  lookup: :google,
  # lookup: :nominatim,         # name of geocoding service (symbol)
  # ip_lookup: :ipinfo_io,      # name of IP address geocoding service (symbol)
  # language: :en,              # ISO-639 language code
  # use_https: false,           # 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)
  api_key: ENV['API_KEY'],
  # api_key: nil,               # API key for geocoding service
  # cache: nil,                 # cache object (must respond to #[], #[]=, and #del)

  # 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

  # Cache configuration
  # cache_options: {
  #   expiration: 2.days,
  #   prefix: 'geocoder:'
  # }
)

lookup: :google
Geocoderが位置情報を取得するために使用するサービスを Googleに指定する。

これで、詳細な住所を指定しても、きちんと地図が生成されるようになりました!

参考にさせていただいた記事

https://zenn.dev/takuyanagai0213/articles/e2467175bdd5fc
https://qiita.com/ryo_1241/items/bdb4babc3fc81eae3b50
https://qiita.com/nagase_toya/items/e49977efb686ed05eadb
https://qiita.com/tiara/items/4a1c98418917a0e74cbb

Discussion