概要
本記事は RUNTEQアドベントカレンダー 2021 の10日目の記事となります!
自分はデリバリーのアルバイトをしているのですが、自分の使っている近道や裏道を保存して誰かと共有できたら良いなあと思いました。
そこで、現在勉強しているRuby,Ruby on Railsを使って道を保存してGoogle Mapに表示させるお試しの機能を作ってみました。
記事を作成した理由
Qiitaの記事にもGoogle mapについての記事はたくさんありますが、道(一連の座標)を保存するやり方についての記事は無いような気がしたので、この記事を作成しました。
初学者ですので間違いがあるかもしれませんが、暖かい目で見て頂ければありがたいです。
この記事で伝えたいこと
- Google Map JavaScript APIのざっくりとした使い方
- 道を1つの文字列として保存できるgemがあること(←ココを一番伝えたい!)
前提環境
MacOS: Catalina
ruby: 3.0.0
ruby on rails: 6.1.4
使用技術
- Google Maps Platform API(Map JavaScript API)
- Ruby gem for Google Maps APIs
Google Maps Platform API(Map JavaScript API)
Google Maps Platform API | Google Developers
googleマップをwebサイトやスマホアプリに表示・カスタマイズするための様々なAPI等を提供しているサービスです。
料金が従量課金制なので、大量のアクセスが見込まれるサイトに使用する場合は注意が必要です。
今回はWebサービスで使いたいのでMap JavaScript APIを使います。
これを使ってマップ上にマーカーとポリラインを作成します。ポリラインとは連続した線で表された線データのことです。
Google Maps APIのためのRuby gem
READMEに使い方が丁寧に書いてあり、とても親切だと思いました。
Google Maps APIの機能を持ったメソッドが使えるようになります。
今回使うのは、一連の座標を文字列として保存できるメソッドです。
Polyline encoder/decoder
これがめちゃ便利だなと思いました。
道をどうやって保存すれば良いのかと思った時に、いくつかの座標を丸ごと保存するのかなー?
でもそうなるとカラムのデータ型は何なるのかな?json型?でも1つのカラムに複数のデータが入るのはよくないよなあ?
なんて思っていたのですが、この機能を使えば座標を文字列として保存できます。
ポリラインの座標を文字列に変換例
path = [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
encoded_path = GoogleMapsService::Polyline.encode(path)
=> "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
文字列をポリラインの座標に変換例
encoded_path = '_p~iF~ps|U_ulLnnqC_mqNvxq`@'
path = GoogleMapsService::Polyline.decode(encoded_path)
=> [{:lat=>38.5, :lng=>-120.2}, {:lat=>40.7, :lng=>-120.95}, {:lat=>43.252, :lng=>-126.45300000000002}]
マジすげえ
機能自体はruby gemだけでなくnpmでも用意されているようです。
https://github.com/googlemaps/js-polyline-codec
実装した流れ
gemのインストール
gem 'google_maps_service'
gem 'gon' # rubyの変数をjsに渡すために必要
コード
ポリライン、マーカの作成
<input type="button" id="save" value="経路を保存する">
<div id="result"></div>
<div id="map"></div>
// 必要なリンク
<script src="https://www.gstatic.com/external_hosted/jquery2.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=自分のAPIKey"></script>
<script>
var poly;
var map;
var save = document.getElementById("save");
var pathValues = [];
function initMap() {
// マップを作成。中心はアメリカのシカゴ
map = new google.maps.Map(document.getElementById("map"), {
zoom: 7,
center: { lat: 41.879, lng: -87.624 },
});
// ポリラインを引く
poly = new google.maps.Polyline({
strokeColor: "#000000",
strokeOpacity: 0.5,
strokeWeight: 10,
});
poly.setMap(map);
map.addListener("click", addLatLng);
}
// 地図上でのクリックイベントを処理し、Polylineに新しいポイントを追加する
function addLatLng(event) {
const path = poly.getPath();
path.push(event.latLng);
// ポリライン上に新しいマーカーを追加する
new google.maps.Marker({
position: event.latLng,
map: map,
});
}
// 座標の配列をPostリクエストをAjaxで送信する
save.addEventListener("click",function() {
var path = poly.getPath();
for (var i = 0; i < path.getLength(); i++) {
pathValues.push(path.getAt(i));
}
$('#result').text('経路を保存しました!');
$.ajax({
url: '/maps',
type: 'POST',
dataType: 'json',
data: {
data: JSON.stringify(pathValues),
},
})
});
$(window).load(initMap);
自分のAPIKeyを取得する方法を記載した公式リンクはこちら
https://cloud.google.com/docs/authentication/api-keys?hl=ja&visit_id=637746662897584655-1175073629&rd=1
元にした公式デモコードはこちら
https://developers.google.com/maps/documentation/javascript/examples/polyline-complex
postリクエストを受け取って保存する
一連の座標を文字列に変更し、mapsテーブルに保存しています。(テーブルの名前はpathsの方がわかりやすかったですね(^^;)
def create
pathValues_json_data = params[:data]
# jsonデータをrubyオブジェクトに変換
pathValues = JSON.parse(pathValues_json_data)
polylines = []
latlng = []
pathValues.each do |path|
latlng = [path["lat"],path"lng"]]
polylines.push(latlng)
end
# ポリラインの座標を文字列に変換
encoded_path = GoogleMapsService::Polyline.encode(polylines)
path = Map.new(encorded_path: encoded_path)
path.save
end
マイグレーションファイル(保存先のテーブル)
class CreateMaps < ActiveRecord::Migration[6.1]
def change
create_table :maps do |t|
# カラムに文字列型を指定
t.string :encorded_path
end
end
end
データを取り出す
def index
paths = Map.all
pathValues = []
maps.each do |map|
# 文字列をポリラインの座標に変換
path = GoogleMapsService::Polyline.decode(map.encorded_path)
pathValues.push(path)
end
# rubyの変数をjsに渡すためにgonを使う
gon.pathValues = pathValues
end
var poly;
var map;
var pathValues = gon.pathValues
function initMap() {
map = new google.maps.Map(document.getElementById("map"), {
zoom: 7,
center: { lat: 41.879, lng: -87.624 }, // Center the map on Chicago, USA.
});
for (var i = 0; i < pathValues.length; i++) {
var path = pathValues[i]
drawPolyline(path);
}
}
function drawPolyline(path) {
poly = new google.maps.Polyline({
path: path,
strokeColor: "#000000",
strokeOpacity: 0.5,
strokeWeight: 10,
});
poly.setMap(map);
}
$(window).load(initMap);
まとめ
改めてコードを見返すと、テーブルの名前はmapsではなかったですね(^^;
その他のコードももっとリファクタできたかもしれません。
今回の機能を作った収穫は座標を文字列で変換できる事を知ることができたことは良かったです。
スマホやGPS受信機を使って一連の座標を取得できればもっと良いなと思いました(^○^)
間違い等あればぜひご指摘いただければ幸いです。