【Rails7】住所を入力しただけで地図(Googleマップ)が自動表示されるようにする

Railsで作成したWebアプリケーションに住所を登録すると、表示画面に自動で地図(Googleマップ)が表示されるようにするための覚え書きです。

この機能を実装するにあたり、住所を登録するためのaddressカラムと、地図の位置情報(緯度・経度)を保存するためのlatitude, longitudeカラムを新たに追加する必要があります。

目次

開発環境

  • Ruby 3.1.2
  • Ruby on Rails 7.0.3
  • Bootstrap 5.1.3
  • M1 Macbook Air 2020
  • mac OS Monterey (ver. 12.4)
  • ターミナル bash (Rosetta 2 使用

出来上がりのイメージ

出来上がりは以下の動作をイメージして作成していきます。

  1. 投稿の住所入力欄に住所、もしくは特定のスポット名称を入力
  2. 投稿後の記事に住所とともに地図(Googleマップ)が表示される
  3. 投稿の住所(もしくはスポット名称)を変更した場合、変更後の場所の地図が表示される

投稿の住所入力欄に住所、もしくは特定のスポット名称(例えば、名古屋城など)を入力すると、投稿記事内に住所とともに地図(Googleマップ)が表示されるようにします。

また、投稿内の住所を変更した場合も、地図の位置も更新されるようにします。

Google Maps の API Key を取得する

今回は地図表示にGoogle Maps JavaScript API(Google Maps Platform) を利用するので、あらかじめGoogle Maps の API Keyを取得しておく必要があります。

あわせて読みたい
Google Maps の API Key を取得する方法 データベースなどの住所情報からGoogleマップを表示する機能を作成する際に、地図の表示にGoogle Maps の API Key が必要になります。 当記事は、そのGoogle Maps の AP...

API Keyを取得したら次に進みます。

データベースに登録した住所から地図(Google Maps)を自動表示する手順

地図表示の大まかな流れは以下の通りです。

  1. 住所(address)と、緯度・経度(latitude, longitude)を保存するカラムを追加する
  2. 投稿から住所情報を入力すると、
  3. gem “geocoder”で住所から緯度・経度を取得してlatitude, longitudeに保存
  4. 投稿を表示する際にlatitude, longitudeから緯度・経度を取得し、
  5. 住所の位置(Googleマップ上)にマーカーをつける

それでは、詳しく見ていきましょう。

取得した API Key を環境変数として設定する

まずは、事前準備としてGoogle Maps Platformで取得しておいたAPI Keyを環境変数として設定しておきます。

Rails で環境変数を設定するためにはdotenv-railsのgemを使用します。

gem "dotenv-rails"

上記をGemfileに追記したら、bundle installします。

次に、アプリディレクトリ直下に.envファイルを新規作成し、以下のようにAPI Keyを記述します。

GOOGLE_MAP_API_KEY = "取得したGoogle Maps APIキー"

最後に、gitのリモートリポジトリに.envに入力したAPIキーをpushしてしまわないように、.gitignoreファイルに以下の記述をします。

・・・

/.env

これで環境変数の設定は終わりです。

以降、APIキーを使う際は以下の環境変数を用います。

ENV['GOOGLE_MAP_API_KEY']

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

住所(address)、緯度(latitude)、経度(longitude)カラムをそれぞれテーブル(今回はPostsテーブル)に追加します。

$ rails g migration add_address_info_to_posts address:string latitude:float longitude:float

この際、緯度と経度の型はfloat型である点に注意しましょう。

class AddAddressInfoToPosts < ActiveRecord::Migration[7.0]
  def change
    add_column :posts, :address, :string
    add_column :posts, :latitude, :float
    add_column :posts, :longitude, :float
  end
end

問題なければマイグレーションを実行してテーブルにカラムを追加します。

$ rails db:migrate

住所入力フォームを作成する

住所を入力するフォームを作成します。

<%= form_with(model: @post, local: true, class: "mt-4 form-column-width") do |f| %>
  ・・・
  <%= f.label :address, class: "form-label fw-bold"%>
  <%= f.text_field :address, autofocus: true,placeholder: "住所を入力してください", class: "form-control" %>
  ・・・
  <%= f.submit (@post.new_record? ? "投稿する" : "更新する"), class: "btn btn-primary mb-4" %>

<% end %>

gem “geocoder” を追加する

入力した住所から緯度と経度を取得するためのgem、geocoderを追加します。

gem "geocoder"

bundle installで追加したら、モデル(ここではPost)に以下のように記述します。

class Post < ApplicationRecord
  geocoded_by :address
  after_validation :geocode
  ・・・
end

これで、Postモデルで住所登録時と変更時にgeocoderが緯度・経度のデータを登録・更新してくれます。

view に Google Maps を表示させるコードを追加する

地図を表示させたいテンプレート(view)に、Google Mapsを表示させるコードを追加します。

<!-- 住所表示エリア -->
<h5>住所</h5>
<p><%= @post.address %></p>

<!-- Googleマップ表示エリア(地図を表示) -->
<div id="map"></div>

<!-- Googleマップ表示用の Javascript -->
<script>
  function initMap(){
    // 地図の位置情報(緯度・経度)を取得
    let mapPosition = {lat: <%= @post.latitude || 0 %> , lng: <%= @post.longitude || 0 %> };
    let map = new google.maps.Map(document.getElementById('map'), {
      zoom: 15,
      center: mapPosition
    });
    let transitLayer = new google.maps.TransitLayer();
    transitLayer.setMap(map);

    let contentString = '【住所】<%= @post.address %>';
    let infowindow = new google.maps.InfoWindow({
      content: contentString
    });

    let marker = new google.maps.Marker({
      position: mapPosition,
      map: map,
      title: contentString
    });

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

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

上記コードの最後の<script src="...の部分で、環境変数として設定したAPI KeyENV['GOOGLE_MAP_API_KEY']を使用します。

これで、地図の表示はできるようになりましたが、このままだと住所によっては地図を表示できない場合があります。

というのも、デフォルトのgeocoderのままでは詳しい住所の経度、緯度を取得することができないからです。

そこで、詳しい住所でも表示されるように設定していきます。

詳細な位置情報(緯度・経度)を取得できるようにする

ターミナル上で以下のコマンドを実行します。

$ rails g geocoder:config

すると、geocoder用の設定ファイルconfig/initializers/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)
  api_key: ENV['GOOGLE_MAP_API_KEY'],               # 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:'
  # }
)

これで、詳しい住所(緯度・経度)も取得できるようになりました。

(Railsサーバーを起動中の場合は、再起動してから読み込むと設定が適用されます)

【Rails7】Google Maps JavaScript API 複数回読み込みエラーの解消方法

Rails7 ではデフォルトでTurbo Driveが有効化されているため、

住所を登録、更新、削除する際に、以下のようにGoogle Maps JavaScript API の複数回読み込みによるエラーが発生してしまいます。

You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors.

これは、住所を登録、更新、削除してページが切り替わる際、Turbo Driveによってページ全体ではなく一部分(<body>タグ内)のみがリフレッシュ(再読み込み)されるために起こります。

ページ全体では再読み込みされていない(最初の1回しか読み込まれていない)状態なのに、ページの<body>タグ内のみ(地図表示で用いるGoogle Maps JavaScript APIを含む)が再度読み込まれるため、

ページ全体から見るとGoogle Maps JavaScript APIが複数回読み込まれたように見えるためです。

このエラーは放置していても地図自体は表示されますが、予期せぬエラーの原因となりかねないのでここで解消しておくのがベターでしょう。

さて、今回のエラーを解消する方法は、ズバリTurbo Driveを無効化することです。

とは言っても、Turbo Driveを無効化するのは住所を登録、更新、削除するリクエストを送信するリンク(ボタン)のみとします。

= f.submit, class: "btn btn-primary mb-4", data: { turbo: false }

= button_to "削除", post_path(@post), class: "btn btn-danger", method: :delete, data: { turbo: false }

上記のように、data: { turbo: false } とすることで、そのリンクや送信リクエストのみTurbo Driveを無効化することができます。

【追記】Turbo Drive無効化による、バリデーション発動時の不具合について

上記のGoogle Maps JavaScript API複数回読み込みエラーへの対処方法としてTurbo Driveの無効化を挙げましたが、Turbo Driveを無効化することで厄介な問題(不具合)が発生。

新規投稿、編集フォームにてバリデーションエラーが発動すると、newもしくはeditがレンダリングされるようにしていましたが、その際にURLが以下のように変わってしまいます。

http://…/posts/new → http://…/posts
http://…/posts/123/edit → http://…/posts/123

ページ自体はレンダリングされるのですが、URLが変更されているため、ユーザーがページの再読み込みを行うとページが変わってしまうという問題が起こります(以下を参照)。

Turbo Driveが有効化されている状態では、バリデーション発動時にnew(もしくはedit)をレンダリングしてもURLは変更されないようTurbo側でうまく調整してくれているみたいですね。

今回の問題を解決する方法の1つとしては、バリデーションエラー時にnew(もしくはedit)をレンダリングするのではなく、redirect_toでnew(もしくはedit)ページを再読み込みする方法があります。

  def create
  ・・・
    if @post.save
      flash[:notice] = "新規投稿が完了しました"
      redirect_to @post
    else
      #--- redirect_to に修正する ---
      redirect_to new_post_path
      #---------------------------- 
    end
  end

  def update
  ・・・
    if @post.update(post_params)
      flash[:notice] = "投稿を更新しました"
      redirect_to @post
    else
      #--- redirect_to に修正する ---
      redirect_to edit_post_path(@post)
      #---------------------------- 
    end
  end

他にも、javascriptでバリデーションエラー時にURLを書き換える方法もあります。

Qiita
renderするとURLが変わり、リロード問題に直面するやつ - Qiita #はい!僕です!!railsでポートフォリオを作成しています。表題の問題にぶち当たり半日消耗したので、記録しておく。#環境ruby2.7rails6.0#問題の詳細例えばblogs_c…

参考資料

Qiita
【Rails】住所を入力すると自動で地図表示される方法(Google Maps API) - Qiita #はじめにポートフォリオ制作で、Google Maps APIを用いて住所フォームを送信した際に住所名と地図を自動で地図表示される設定を行ったので、アウトプットも兼ねて手順を紹...
Qiita
Rails5でGoogleMapを表示してみるまで - Qiita はじめにInfratopという会社でプログラミングスクールでメンターをしていますした。最近、GoogleMapで地図出したいんだけど上手く出ないという質問がありまして、この際、...
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

愛知の34歳。無職で暇になり始めたプログラミング(Ruby on Rails)の忘備録をまとめたブログです。最近は別にやりたいことができたのでプログラミングほぼやっていません。気が向いたらまた再開するかも。僕の日常はメインブログの方で更新しています。

コメント

コメントする

目次