jsフレームワークstimulusでyahoo地図を操作してみた（その三）

だいぶ間隔が空いてしまったけど、[前回](https://qiita.com/wkya_4a/items/3e2f5bb6cf8abe525bf0)の続きです。

# やったこと、やる予定のこと
#### 0. [railsアプリへのstimulus適用](https://qiita.com/wkya_4a/items/e85bd6324fe4085dce0e)
#### 1. [stimuluｓでyahoo地図を表示](https://qiita.com/wkya_4a/items/6e1224550ca0b7aed9d9)

#### ２. [表示した地図上で、指定座標に移動](https://qiita.com/wkya_4a/items/3e2f5bb6cf8abe525bf0)（前回）

#### 3. 住所から座標を取得し、それをDBに登録（←今ここ）

#### 4.[「3」で登録した情報を選択し、yahoo地図上でその場所に移動（最終回）](https://qiita.com/wkya_4a/items/3df3c39c0c779ce2f452)

今回は「住所から座標の取得」……つまりはジオコーディングです。
ただ取得したデータの登録までやりたいのでーー

##先に、登録先のテーブルを作っておく。

####テーブル名：「maps」
→「名前（name）」「住所（address）」「緯度（latitude）」「経度（longitude）」の情報（列）を持たせる

```ターミナル:ターミナル

$ rails g scaffold maps name:string address:string latitude:string longitude:string

$ rails db:migrate

```
で、とりあえず座標登録できるようになる。
![スクリーンショット 2019-06-24 11.34.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410674/35c4ff88-e41b-0e18-de54-d04412ff3e47.png)

……まあ現時点では、手で直接入力すれば、なんですが。


##次に、ビュー関係を整えておく。

復習がてら、この画面にもyahooの地図が表示されるようにしておこう。

```erb:app/views/maps/_form.html.erb

<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>

```

```js:app/javascript/controllers/geocode_controller.js

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」にすでに書き込んである前提です
（ココらへんで不明点があれば、[前々回の記事](https://qiita.com/wkya_4a/items/6e1224550ca0b7aed9d9)を参照してください）。


で、ついでに住所入力欄の隣に、座標取得用のボタンも用意しておく。

```erb:app/views/maps/_form.html.erb

  <div class="field">
    <%= form.label :address %>
    <%= form.text_field :address %>
    <input type="button" value="座標取得"><!-- ←追記 -->
  </div>

```
画面を確認すると、こんな感じ（地図をそのまま乗せるのはまずいっぽいので、モザイクを書けています）。
![スクリーンショット 2019-06-24 12.14.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410674/7f1fe84e-a47f-be69-0362-1013de33593f.png)



この画面で実現させたいのは、
・住所を入力する
　　↓
・「座標取得」ボタンを押す
　　↓
・住所の地点にピンが立ち、その座標が「緯度」「経度」に入力される
　　↓
・ピンの場所を確認し、問題なければ登録
の流れ。

これをstimulas的（？）に考えると

①【data-action：「座標取得」ボタンのクリック】が発生した時に、
②【data-target：「住所」欄の情報を取得し】
③そこからジオコーディングで座標情報を獲得して
④【data-target：「緯度」「経度」に表示　＋　「地図」にピンを立てる】

となる。

地図はすでにターゲット指定しているので、残りにもアクション、ターゲットの指定をしておこう。

```erb:app/views/maps/_form.html.erb

  <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は[ここ](https://easyramble.com/yolp-geocoder-api-with-ruby.html)、BとCは[ここ（公式）](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/js/)と[ここ](http://gmap.pw/api_v3/maker_hide/all/)の記述を参考にさせていただきました。

####まずは下準備

```js:app/javascript/controllers/geocode_controller.js

import { Controller } from "stimulus"

// ピン保存用の枠……「D」で削除するために、作ったピンは保存しておく必要がある
var markers = [];

export default class extends Controller {
  static targets = [ "map", "address","latitude","longitude"  ]
                             //↑住所，緯度，経度を追加

  initialize() {

// 以下、略

```
追加されたターゲット、および「作成したピンの保存場所（後で削除するかもしれないので）」を用意しておく。

####次いで、A.【住所から座標を獲得する】……の前に、
この【住所から座標を獲得する】処理は、**非同期**で行われる。
で、stimulusでは「this.【ターゲット名】Target」でビュー要素の取得や設定を行うのだが、非同期処理中だとこの記述では**要素を取得出来ない**。

なので非同期処理が始まる前に、使用する要素を別の変数に格納しておく（ここで一日詰まって、いろいろ試してみて見つけた「動かせる方法」がこのやり方。もっといい方法が分かる方入れば、ご教授よろしくおねがいします）。


```js:app/javascript/controllers/geocode_controller.js

  get_lat_lng() {

    //ターゲットを変数に移す
    var address = this.addressTarget.value;
    var latitude = this.latitudeTarget
    var longitude = this.longitudeTarget;

    var map_box = this.map

    // 続く

```

####あらためて、A.【住所から座標を獲得する】
```js:app/javascript/controllers/geocode_controller.js

    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」の前にすでにピンが立っていた場合、それを削除する】

```js:app/javascript/controllers/geocode_controller.js

//略

/////↓↓ここ（非同期処理中）で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.【獲得した座標の場所にピンを立てる】

```js:app/javascript/controllers/geocode_controller.js

//略
/////↓↓ここ（非同期処理中）で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.【獲得した座標情報をビューに表示する】
```js:app/javascript/controllers/geocode_controller.js

//略
/////↓↓ここ（非同期処理中）でB,C,Dの処理を行う↓↓////////////////////
//Cの処理          
latitude.value =ydf.features[0]["latlng"]["Lat"];
longitude.value =ydf.features[0]["latlng"]["Lon"];

/////↑↑、ここ（非同期処理中）でB,C,Dの処理を行う↑↑////////////////////
//略
```

これで**座標を取得できる住所であれば**、その座標の表示とピン作成ができるように……
![スクリーンショット 2019-06-24 14.08.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/410674/e6c7158a-3d67-d061-e8ec-de959c1018dc.png)

なりました。

ちなみに住所からではなく地図を直接クリックして、【クリックした地点の座標を取得】することもできる（これについてはまた別の記事にまとめる予定）。
これで結構簡単に座標登録できるようになりそうだ……と、思ったけど、、、

##でもこれって、登録しちゃっていいの!!??
ここまで作ったあとでふと、[APIのFAQ](https://map.yahoo.co.jp/promo/yolp/faq.html#14)に目をやってみたら……
＞API経由で取得したデータを保存したり、二次利用することはできません。
……あれ、俺、規約違反してる？

##大丈夫でした!!!
Yahoo! JAPANカスタマーセンターに確認したところ、問題ないそうです
（詳細については、また別の記事を書く予定）。
ご対応いただいたYahoo! JAPANカスタマーサービスの担当者様、どうもありがとうございました。

###ということで、さっそく自作アプリに使ってみました。
[ルート作成ツール【Maっぷら】、新規登録画面](http://mappura.com/drawing_pins/new)
……「座標→住所」「住所→座標」の両方を実装できたので、使い勝手もだいぶ良くなったと思います。