だいぶ間隔が空いてしまったけど、前回の続きです。
やったこと、やる予定のこと
0. railsアプリへのstimulus適用
1. stimulusでyahoo地図を表示
2. 表示した地図上で、指定座標に移動(前回)
3. 住所から座標を取得し、それをDBに登録(←今ここ)
4.「3」で登録した情報を選択し、yahoo地図上でその場所に移動(最終回)
今回は「住所から座標の取得」……つまりはジオコーディングです。
ただ取得したデータの登録までやりたいのでーー
##先に、登録先のテーブルを作っておく。
####テーブル名:「maps」
→「名前(name)」「住所(address)」「緯度(latitude)」「経度(longitude)」の情報(列)を持たせる
$ rails g scaffold maps name:string address:string latitude:string longitude:string
$ rails db:migrate
……まあ現時点では、手で直接入力すれば、なんですが。
##次に、ビュー関係を整えておく。
復習がてら、この画面にもyahooの地図が表示されるようにしておこう。
<div data-controller="geocode"><!-- ←追記 -->
<%= form_with(model: map, local: true) do |form| %><!-- scaffoldで出来た記述 -->
<!-- 中略 -->
<!-- ↓↓scaffoldで出来た記述↓↓ -->
<div class="actions">
<%= form.submit %>
</div>
<% end %>
<!-- ↑↑scaffoldで出来た記述↑↑ -->
<!-- ↓↓以下、追記↓↓ -->
<div id="idmeihatekitoudemoiiyo" style="width:800px; height:400px" data-target="geocode.map" ></div>
</div>
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "map" ]
initialize() {
this.map = new Y.Map(this.mapTarget.id);
this.map.drawMap(new Y.LatLng(35.66572, 139.73100), 17, Y.LayerSetId.NORMAL);
var center = new Y.CenterMarkControl
var control = new Y.LayerSetControl();
this.map.addControl(center);
this.map.addControl(control);
}
}
今回は「data-controller名」を「geocode」にする(ので、jsのファイル名は「geocode_controller.js」になる)。
またyahooのAPIは「application.html.erb」にすでに書き込んである前提です
(ココらへんで不明点があれば、前々回の記事を参照してください)。
で、ついでに住所入力欄の隣に、座標取得用のボタンも用意しておく。
<div class="field">
<%= form.label :address %>
<%= form.text_field :address %>
<input type="button" value="座標取得"><!-- ←追記 -->
</div>
画面を確認すると、こんな感じ(地図をそのまま乗せるのはまずいっぽいので、モザイクを書けています)。
この画面で実現させたいのは、
・住所を入力する
↓
・「座標取得」ボタンを押す
↓
・住所の地点にピンが立ち、その座標が「緯度」「経度」に入力される
↓
・ピンの場所を確認し、問題なければ登録
の流れ。
これをstimulas的(?)に考えると
①【data-action:「座標取得」ボタンのクリック】が発生した時に、
②【data-target:「住所」欄の情報を取得し】
③そこからジオコーディングで座標情報を獲得して
④【data-target:「緯度」「経度」に表示 + 「地図」にピンを立てる】
となる。
地図はすでにターゲット指定しているので、残りにもアクション、ターゲットの指定をしておこう。
<div class="field">
<%= form.label :address %>
<!-- ↓↓「住所」をターゲット指定↓↓ -->
<%= form.text_field :address , :data => {:target=>"geocode.address"} %>
<!-- ↓↓「座標獲得ボタンのクリック」をアクション指定↓↓ -->
<input type="button" value="座標取得" data-action="click->geocode#get_lat_lng">
</div>
<div class="field">
<%= form.label :latitude %>
<!-- ↓↓「緯度」をターゲット指定↓↓ -->
<%= form.text_field :latitude, :data => {:target=>"geocode.latitude"} %>
</div>
<div class="field">
<%= form.label :longitude %>
<!-- ↓↓「経度」をターゲット指定↓↓ -->
<%= form.text_field :longitude , :data => {:target=>"geocode.longitude"} %>
</div>
ーーこれでビュー側の作業は完了のはず。
##最後に、stimulus側の記述をしていく
stimulus側で行うことを大別すると、
A.【住所から座標を獲得する】
B.【獲得した座標の場所にピンを立てる】
C.【獲得した座標情報をビューに表示する】
そして
D.【「B」の前にすでにピンが立っていた場合、それを削除する】
となる(初めはDをうっかり忘れていて、何本もピンが立つ状態にしてしまった)。
ちなみにAはここ、BとCはここ(公式)とここの記述を参考にさせていただきました。
####まずは下準備
import { Controller } from "stimulus"
// ピン保存用の枠……「D」で削除するために、作ったピンは保存しておく必要がある
var markers = [];
export default class extends Controller {
static targets = [ "map", "address","latitude","longitude" ]
//↑住所,緯度,経度を追加
initialize() {
// 以下、略
追加されたターゲット、および「作成したピンの保存場所(後で削除するかもしれないので)」を用意しておく。
####次いで、A.【住所から座標を獲得する】……の前に、
この【住所から座標を獲得する】処理は、非同期で行われる。
で、stimulusでは「this.【ターゲット名】Target」でビュー要素の取得や設定を行うのだが、非同期処理中だとこの記述では要素を取得出来ない。
なので非同期処理が始まる前に、使用する要素を別の変数に格納しておく(ここで一日詰まって、いろいろ試してみて見つけた「動かせる方法」がこのやり方。もっといい方法が分かる方入れば、ご教授よろしくおねがいします)。
get_lat_lng() {
//ターゲットを変数に移す
var address = this.addressTarget.value;
var latitude = this.latitudeTarget
var longitude = this.longitudeTarget;
var map_box = this.map
// 続く
####あらためて、A.【住所から座標を獲得する】
var map_box = this.map
// 続き
var request = { query : address };
var geocoder = new Y.GeoCoder();
geocoder.execute( request , function( ydf ) {//←ジオコーディング処理
//成功すれば↓の式が正となり、「ydf」に座標情報が獲得される
if ( ydf.features.length > 0 ) {
/////↓↓ので、ここ(非同期処理中)でB,C,Dの処理を行う↓↓////////////////////
/////↑↑ので、ここ(非同期処理中)でB,C,Dの処理を行う↑↑////////////////////
}else{
//【住所から座標を獲得できなかった場合の処理……今回は抜けるだけでいいや】
return;
}
} );
####続けて、座標情報を取得した場合の処理
#####・まずはD.【「B」の前にすでにピンが立っていた場合、それを削除する】
//略
/////↓↓ここ(非同期処理中)でB,C,Dの処理を行う↓↓////////////////////
//Dの処理
if(markers.length > 0){
for (var i = 0; i < markers.length; i++) {
map_box.removeFeature(markers[i]);
}
markers = []; //参照を開放
}
/////↑↑、ここ(非同期処理中)でB,C,Dの処理を行う↑↑////////////////////
//略
「markers」に格納しているピンを(存在すれば)削除する。
ピンが複数あっても対応できるように「for」で回しているけど、今回の場合なら
map_box.removeFeature(markers[0]);
でも問題ないはず。
#####・続けてB.【獲得した座標の場所にピンを立てる】
//略
/////↓↓ここ(非同期処理中)でB,C,Dの処理を行う↓↓////////////////////
//Dの処理
var current_location = new Y.LatLng(ydf.features[0]["latlng"]["Lat"],ydf.features[0]["latlng"]["Lon"])
var marker = new Y.Marker(current_location);
map_box.addFeature(marker);
// // 作成したマーカーを保存(削除できるように)
markers.push(marker);
// ピンの場所に移動
map_box.panTo(current_location, true);
/////↑↑、ここ(非同期処理中)でB,C,Dの処理を行う↑↑////////////////////
//略
作成したピンを、「markers」に保存しておくことを忘れないように(でないと「D」の意味がなくなる)。
#####・最後にC.【獲得した座標情報をビューに表示する】
//略
/////↓↓ここ(非同期処理中)でB,C,Dの処理を行う↓↓////////////////////
//Cの処理
latitude.value =ydf.features[0]["latlng"]["Lat"];
longitude.value =ydf.features[0]["latlng"]["Lon"];
/////↑↑、ここ(非同期処理中)でB,C,Dの処理を行う↑↑////////////////////
//略
これで座標を取得できる住所であれば、その座標の表示とピン作成ができるように……
なりました。
ちなみに住所からではなく地図を直接クリックして、【クリックした地点の座標を取得】することもできる(これについてはまた別の記事にまとめる予定)。
これで結構簡単に座標登録できるようになりそうだ……と、思ったけど、、、
##でもこれって、登録しちゃっていいの!!??
ここまで作ったあとでふと、APIのFAQに目をやってみたら……
>API経由で取得したデータを保存したり、二次利用することはできません。
……あれ、俺、規約違反してる?
##大丈夫でした!!!
Yahoo! JAPANカスタマーセンターに確認したところ、問題ないそうです
(詳細については、また別の記事を書く予定)。
ご対応いただいたYahoo! JAPANカスタマーサービスの担当者様、どうもありがとうございました。
###ということで、さっそく自作アプリに使ってみました。
ルート作成ツール【Maっぷら】、新規登録画面
……「座標→住所」「住所→座標」の両方を実装できたので、使い勝手もだいぶ良くなったと思います。