はじめに
ポートフォリオにGoogleMap APIを導入しました
一つの投稿に複数の住所が登録されている可能性があるため
複数の住所が表示できるように実装しました!
アウトプットします✍️
※旅行やデートのプランを共有するアプリなので、投稿機能はPostではなくPlanを使用しています!
また、Plansの子要素としてPlanDetailsモデルがあり、そこにaddressカラムがあります!
GoogleMap APIキーとGeocodingAPIを取得
導入手順は割愛させていただきます!
こちらの記事に詳しく手順が紹介されていましたので、
ご確認ください!
ER図
事前準備
gemをインストール
:
gem 'dotenv-rails'
gem 'geocoder'
$ bundel install
Javascriptで環境変数を読み込めるようにするため以下のコマンドを実行
$ yarn add dotenv
先ほど取得したAPIキーをenvファイルに記述
Maps_API_Key="ここにAPIキーを記述"
Geocoding_API_Key="ここにAPIキーを記述"
Javascriptで環境変数を読み込めるように記述
// この行を追加
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
上記のコマンドを実行するとファイルが生成されます
もし生成されない場合は、手動作成しても問題ありません
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
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を扱えるようにする
class PlanDetail < ApplicationRecord
# アソシエーション
belongs_to :plan
# Google.map API
# addressカラムを緯度経度に変換
geocoded_by :address
# latitudeカラム・longitudeカラムに緯度・経度の値を入力
after_validation :geocode
end
入力フォームに住所を登録できるようにする
<%= 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に記述します!
<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>
<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>
<% @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
には経度を取得
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 を地図の中心に設定します
let transitLayer = new google.maps.TransitLayer();
transitLayer.setMap(map);
google.maps.TransitLayer
地図上に公共交通機関の情報(電車やバスの路線、停留所)を追加するためのクラス
transitLayer.setMap(map);
先に作成した mapオブジェクトに公共交通機関の情報が表示される
<% map = true %>
<% @plan_details.each do |pd| %>
<% if map %>
: 1回目に取得したaddressをマップの中心にする記述
<% map = false %>
<% end %>
:
<% end %>
each文の最初の1回だけ地図を初期化し、その後は地図の中心を変更しないための記述
map = true
の時のみcenter:
が実行され、さいごにmap = false
に変更することにより再度中心を変更することを防いでいます!
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をウィンドウに表示されるよう指定しています
let marker<%= pd.id %> = new google.maps.Marker({
position: mapPosition<%= pd.id %>,
map: map,
title: contentString<%= pd.id %>
});
new google.maps.Marker
この記述でマーカーを作成しています(赤いピンの部分です)
position: mapPosition<%= pd.id %>,
マーカーの位置を指定します
ここでは、先に作成した mapPosition<%= pd.id %> を指定しています
map: map,
マーカーやウィンドウなどの地図要素をどのmapインスタンスに関連付けるかを指定しています
今回は先ほど作成した map です
title: contentString<%= pd.id %>
マーカーのタイトルを設定します
ここでは、先に作成した contentString<%= pd.id %> を指定しています
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表示方法でした!
難しかったです😭
参照記事
参考にさせていただきました!
ありがとうございました!