0
0

Rails ポートフォリオにGoogleMap APIを導入 複数の住所を表示

Posted at

はじめに

ポートフォリオにGoogleMap APIを導入しました
一つの投稿に複数の住所が登録されている可能性があるため
複数の住所が表示できるように実装しました!
アウトプットします✍️

※旅行やデートのプランを共有するアプリなので、投稿機能はPostではなくPlanを使用しています!
また、Plansの子要素としてPlanDetailsモデルがあり、そこにaddressカラムがあります!

GoogleMap APIキーとGeocodingAPIを取得

導入手順は割愛させていただきます!
こちらの記事に詳しく手順が紹介されていましたので、
ご確認ください!

ER図

スクリーンショット 2024-06-17 21.15.41.png

事前準備

gemをインストール

:
gem 'dotenv-rails'
gem 'geocoder' 
$ bundel install

Javascriptで環境変数を読み込めるようにするため以下のコマンドを実行

$ yarn add dotenv

先ほど取得したAPIキーをenvファイルに記述

.env
Maps_API_Key="ここにAPIキーを記述"
Geocoding_API_Key="ここにAPIキーを記述"

Javascriptで環境変数を読み込めるように記述

config/webpack/environment.js
// この行を追加
require("dotenv").config(); 

const { environment } = require('@rails/webpacker')

module.exports = environment

const webpack = require('webpack')
environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: 'popper.js'
  })
)

geocoderの設定

geocoderは位置情報を扱うGemです
住所カラムから緯度、経度を変換する機能を使用するため導入します

$ rails g geocoder:config

上記のコマンドを実行するとファイルが生成されます
もし生成されない場合は、手動作成しても問題ありません

config/intializers/geocoder.rb
Geocoder.configure(
  lookup: :google,
  use_https: true,
  api_key: ENV["Geocoding_API_Key"],
  units: :km
)

コメントアウトを戻すなどして、上記の設定をしましょう

住所・緯度・経度カラムの追加

PlanDetailモデルに住所と、緯度と経度のカラムを追加します

$ rails g migration AddGeocodingColumnToPlanDetails address:string latitude:float longitude:float
xxxxx_add_geocoding_colum_to_plan_details.rb
class AddGeocodingColumnToPlanDetails < ActiveRecord::Migration[6.1]
  def change
    add_column :plan_details, :address, :string, null: false, default: ""
    add_column :plan_details, :latitude, :float, null: false, default: 0
    add_column :plan_details, :longitude, :float, null: false, default: 0
  end
end
$ rails db:migrate

PlanDetailモデルでgeocodingを扱えるようにする

app/models/plan_detail.rb
class PlanDetail < ApplicationRecord

  # アソシエーション
  belongs_to :plan

  # Google.map API
  # addressカラムを緯度経度に変換
  geocoded_by :address
  # latitudeカラム・longitudeカラムに緯度・経度の値を入力
  after_validation :geocode

end

入力フォームに住所を登録できるようにする

new.html.erb
<%= form_with model: @plan do |f| %>
:
  <%= plan_detail.text_field :address, class: "form-control mb-3", placeholder: "住所" %>
:
<%= f.submit '投稿', class: 'btn btn-warning mx-auto px-4' %>

viewにmapを表示させるため記述

今回は投稿画面に表示させたいため、show.html.erbに記述します!

show.html.erb
<div id="map"></div>

<script>
  function initMap(){
    <% map = true %>
    <% @plan_details.each do |pd| %>
      <% next if pd.address.blank? %>
    let mapPosition<%= pd.id %> = {lat: <%= pd.latitude %> , lng: <%= pd.longitude %> };

    <% if map %>
    let map = new google.maps.Map(document.getElementById('map'), {
      zoom: 12,
      center: mapPosition<%= pd.id %>
    });
    let transitLayer = new google.maps.TransitLayer();
    transitLayer.setMap(map);
    <% map = false %>
    <% end %>

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

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

    marker<%= pd.id %>.addListener('click', function(){
      infowindow<%= pd.id %>.open(map, marker<%= pd.id %>);
    });

    <% end %>
  }
</script>

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

スクリーンショット 2024-06-17 22.20.12.png

<div id="map"></div>
mapを表示させたい場所に記述します
大きさを変えたい場合はbootstrapやcssを使用して修正してください
例)<div id="map" class="col-sm-12 col-md-10 col-lg-8 mx-auto" style="height: 300px;"></div>

show.html.erb
<% @plan_details.each do |pd| %>
  <% next if pd.address.blank? %>
    let mapPosition<%= pd.id %> = {lat: <%= pd.latitude %> , lng: <%= pd.longitude %> };
:
<% end %>

<% @plan_details.each do |pd| %>
viewにJavascriptを記述しているため、railsの記述をすることができます
今回はplan_detailの数だけaddressがあるため(正確には、必須ではないためaddressはない可能性もあります)each文を使用してaddress分のピンを立てます

<% next if pd.address.blank? %>
each分の中で使用できるif文の書き方です
addressがブランクの場合飛ばして次にいきます(plan_detail全てにaddressが入っているわけではないため)<% end %>の記述は不要です!

let mapPosition<%= pd.id %> = {lat: <%= pd.latitude %> , lng: <%= pd.longitude %> };
let mapPosition<%= pd.id %>という形で、各プラン詳細ごとに異なる位置情報(緯度・経度)をを定義していますlatにはeach文から緯度を取得し、lngには経度を取得

show.html.erb
    let map = new google.maps.Map(document.getElementById('map'), {
      zoom: 12,
      center: mapPosition<%= pd.id %>
    });

new google.maps.Map
新しい地図オブジェクトを作成するための記述です

(document.getElementById('map')
HTMlドキュメント内のid属性が map である要素を取得します
今回では<div id="map"></div>の部分です

center: mapPosition<%= pd.id %>
先ほど取得した mapPosition を地図の中心に設定します

show.html.erb
    let transitLayer = new google.maps.TransitLayer();
    transitLayer.setMap(map);

google.maps.TransitLayer
地図上に公共交通機関の情報(電車やバスの路線、停留所)を追加するためのクラス

transitLayer.setMap(map);
先に作成した mapオブジェクトに公共交通機関の情報が表示される

show.html.erb
<% map = true %>
  <% @plan_details.each do |pd| %>
    
<% if map %>
: 1回目に取得したaddressをマップの中心にする記述
<% map = false %>
<% end %>
:
  <% end %>

each文の最初の1回だけ地図を初期化し、その後は地図の中心を変更しないための記述
map = trueの時のみcenter:が実行され、さいごにmap = falseに変更することにより再度中心を変更することを防いでいます!

show.html.erb
    let contentString<%= pd.id %> = '【住所】<%= pd.address %>';
    let infowindow<%= pd.id %> = new google.maps.InfoWindow({
      content: contentString<%= pd.id %>
    });

let contentString<%= pd.id %> = '【住所】<%= pd.address %>';
マーカーをクリックしたときに出てくるウィンドウに表示する情報を格納しています
今回はpdから取得したaddressです

let infowindow<%= pd.id %> = new google.maps.InfoWindow({
情報ウィンドウオブジェクトを格納する変数に、ウィンドウを作成し代入しています

content: contentString<%= pd.id %>
ここで、先ほど取得したpdのaddressをウィンドウに表示されるよう指定しています

show.html.erb
    let marker<%= pd.id %> = new google.maps.Marker({
      position: mapPosition<%= pd.id %>,
      map: map,
      title: contentString<%= pd.id %>
    });

new google.maps.Marker
この記述でマーカーを作成しています(赤いピンの部分です)
スクリーンショット 2024-06-17 23.25.34.png

position: mapPosition<%= pd.id %>,
マーカーの位置を指定します
ここでは、先に作成した mapPosition<%= pd.id %> を指定しています

map: map,
マーカーやウィンドウなどの地図要素をどのmapインスタンスに関連付けるかを指定しています
今回は先ほど作成した map です

title: contentString<%= pd.id %>
マーカーのタイトルを設定します
ここでは、先に作成した contentString<%= pd.id %> を指定しています

show.html.erb
    marker<%= pd.id %>.addListener('click', function(){
      infowindow<%= pd.id %>.open(map, marker<%= pd.id %>);
    });

marker<%= pd.id %>.addListener('click', function(){…})
マーカーがクリックされたときに指定された関数が実行されます

infowindow<%= pd.id %>.open(map, marker<%= pd.id %>);
マーカーがクリックされたときに指定された関数そのものです
今回はクリックされたときに情報ウィンドウが表示されます

さいごに

複数の住所がある場合の、GoogleMap表示方法でした!
難しかったです😭

参照記事

参考にさせていただきました!
ありがとうございました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0