RailsでGoogle mapの位置情報を扱うモデルを作成する

2023/02/25

GoogleMap
Rails

こんにちは。

今回は、RailsでGoogle mapの位置情報を扱うモデルを作成する方法をご紹介します。

Google mapとは

Google mapは、Googleが提供している地図サービスです。

Google map

Google map

このような地図をアプリ内に埋め込み、マーカーを表示するまでを今回はご紹介します。

Google mapのAPIキーを取得する

Google mapを使うには、APIキーが必要になります。

Google map APIキーの取得方法の公式ドキュメント

GCPのプロジェクトを作成し、上記の公式ドキュメントを参考にAPIキーを取得します。

develop環境で試す分には特にAPIキーの制限は必要ないと思いますが、本番環境で使う場合は、制限を設けることをおすすめします。

developで使用しているキーは流出した場合、課金が発生する可能性があるためこまめにAPIキーを変更することをおすすめします。

RailsのcredentialsにAPIキーを設定する

RailsのcredentialsにAPIキーを設定します。

EDITOR="code --wait" bin/rails credentials:edit
config/credentials.yml.enc
gcp:
  maps_platform_api_key: XXXXXXXXXXX

上記の設定で、以下のようにAPIキーを取得することができます。

key = Rails.application.credentials.dig(:gcp, :maps_platform_api_key)

Railsサーバーの再起動が必要です。

位置情報を扱うモデルを作成する

位置情報を扱うモデルとして、今回はAddressモデルを作成します。

Addressモデルのスキーマは以下のようになります。

db/schema.rb
create_table :addresses, force: :cascade do |t|
  t.references :addressable, polymorphic: true
  t.string :postal_code, null: false
  t.string :text, null: false
  t.string :building
  t.string :access, null: false
  t.float :latitude, null: false
  t.float :longitude, null: false
  t.string :place_id, null: false
  t.string :prefecture, null: false
  t.timestamps null: false
end

Addressableは、Addressモデルを関連付けるためのカラムです。
ポリモーフィック関連付けを使っています。

Addressモデルの内部は以下のようになります。

※ 必要な部分のみに絞っています。
次回、途中の文字列(郵便番号など)を入力すると、自動で住所を補完する機能に必要なコードもご紹介しますね。

app/models/address.rb
class Address < ApplicationRecord # rubocop:disable Metrics/ClassLength

  # アソシエーション
  belongs_to :addressable, polymorphic: true

  # バリデーション
  validates :postal_code, presence: true
  validates :text, presence: true
  validates :access, presence: true
  validates :longitude, presence: true
  validates :latitude, presence: true
  validates :place_id, presence: true
  validates :prefecture, presence: true

  # エラークラスの定義
  class ZeroResultError < StandardError; end
  class InvalidAddressError < StandardError; end

  def self.initialize_with_full_text(text:)
    body = fetch_address(text:)
    new(formatted_params_for_full_text(body))
  end

  class << self
    private

    def fetch_address(text:)
      api_key = Rails.application.credentials.dig(:gcp, :maps_platform_api_key)
      encoded_text = URI.encode_www_form_component(text)
      uri = URI.parse("https://maps.googleapis.com/maps/api/geocode/json?address=#{encoded_text}&language=ja&components=country:JP&key=#{api_key}")
      res = Net::HTTP.get_response(uri)

      raise ActiveRecord::RecordNotFound if res.code.to_i == 404
      raise StandardError unless res.code.to_i == 200
      raise ZeroResultError if JSON.parse(res.body, symbolize_names: true)[:status] == 'ZERO_RESULTS'

      JSON.parse(res.body, symbolize_names: true)
    end

    def formatted_params_for_full_text(body)
      text = body.dig(:results, 0, :formatted_address).split[1].tr('0-9', '0-9')

      address_components = body.dig(:results, 0, :address_components)
      postal_code = address_components.find do |component|
        component[:types].include?('postal_code')
      end.try(:dig, :long_name).try(:tr, '0-9', '0-9')

      location = body.dig(:results, 0, :geometry, :location)
      latitude = location[:lat]
      longitude = location[:lng]

      place_id = body.dig(:results, 0, :place_id)

      prefecture = address_components.find do |component|
        component[:types].include?('administrative_area_level_1')
      end.try(:dig, :long_name)

      {
        postal_code:,
        text:,
        latitude:,
        longitude:,
        place_id:,
        prefecture:
      }
    rescue StandardError
      raise InvalidAddressError
    end
  end
end

Address.initialize_with_full_text(text:)を呼び出すと、Google mapのAPIを叩いて、それに紐づくAddressモデルのインスタンスを返します。

fetch_address(text:)は、Google mapのAPIを叩いて、その結果を返すメソッドになっています。

formatted_params_for_full_text(body)は、Google mapのAPIの結果を元に、Addressモデルのインスタンスを作成するためのパラメータを返すメソッドになっています。

これで、フォームから住所のテキスト情報を受け取ったら、Address.initialize_with_full_text(text:)を呼び出すことで、住所の情報を取得することができます。

Google mapを表示する

入った住所情報をもとに、Google mapを表示します。

Google mapの表示方法には、JavaScriptを使用する方法(= Maps JavaScript API)とHTMLのみで表示する方法(= Maps Embed API)があります。

Google Maps Platformのドキュメント

単純に、一つのマーカーを表示するだけであれば、Maps Embed APIを使うのが簡単ですので今回はこちらを使用します。

またMaps JavaScript APIについても、次回の記事でご紹介します。

Maps Embed APIを使う

Maps Embed APIを使うには、以下のように<iframe>タグを使用します。

app/components/addresses/map.html.haml
%iframe.w-full.h-80{:src => map_url, :allowfullscreen => "", :loading => "lazy", :referrerpolicy => "no-referrer-when-downgrade"}

map_urlは、以下のようにコンポーネント内のメソッドとして作成します。

app/components/addresses/map.rb
class Addresses::Map::Component < ViewComponent::Base
  def initialize(place_id:)
    @place_id = place_id
  end

  private

  def map_url
    key = Rails.application.credentials.dig(:gcp, :maps_platform_api_key)
    "https://www.google.com/maps/embed/v1/place?key=#{key}&q=place_id:#{@place_id}"
  end
end

view内で、以下のように呼び出します。

app/views/addresses/show.html.haml
= render Addresses::Map::Component.new(place_id: @address.place_id)

これで、Google mapが表示されます。

まとめ

今回は、Google mapのAPIを使って、住所の情報を取得する方法をご紹介しました。

次回、郵便番号から住所を取得する方法をご紹介します。

Related Posts

microCMSをrailsで使う

microCMSをrailsで使う

[Rails] view_componentというgemがすごく便利なので紹介します

[Rails] view_componentというgemがすごく便利なので紹介します