[Rails]アプリに天気表示機能を追加した（Open Weather API）

#概要
Open Weather APIを利用して、Spotsテーブルの座標を元に、今日明日の天気予報を表示する機能を実装しました。備忘録として記します。

##環境
- Ruby 2.7.2
- Rails 6.0.3.4


#Railsアプリに天気予報表示を追加する流れ
① **[OpenWeather](https://openweathermap.org/)** に登録し、`APIキーを取得`する（無料）
② アプリに表示させたい気象項目を決める（天気アイコン・気温・風向き等）
③ 目的に合うAPIを選択する（`現在の気象情報`や`30日分の天気予報`等、10種類のAPIがある）
④ どのような `JSONデータ` が取得できるか、Chrome拡張の **[Advanced REST Client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=JA)** を使って確認 
⑤ API呼び出しコードをJSファイルに記述
⑥ 生成されたhtmlのスタイルを調整して完成！

----
私の場合、下記条件で作成したので、特に同じような方には参考になるかもしれません。

- **天気アイコン・日付・風向き・風速**を表示する
- 座標を元に観測地点を特定する
- APIキーを `config/credentials.yml.enc` ファイルで管理する

<img width="160" alt="api-weather-icon.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646452/33324a29-f1ad-96aa-92d6-a01c57974fa4.jpeg">

#コード解説

## 0. 最終的なコード

詳細を順に解説していきます。

```html:weather_api.html.erb
<div class="weather">
  <div id="weather"></div>
</div>
<%= javascript_pack_tag 'weather_api' %>

<!-- 天気予報を表示するビューを作成 -->
```
```rb:spots_controller.rb
def show
  @spot = Spot.find(params[:id])
  gon.spot = @spot
  gon.api = Rails.application.credentials.open_weather_api_key
end

#Railsで定義した変数を、JavaScriptで使えるよう代入
```
```js:weather_api.js
$(function () {
  const lat = gon.spot.latitude
  const lon = gon.spot.longitude
  const key = gon.api
  // 天気予報を取得
  const weather_url = 'https://api.openweathermap.org/data/2.5/onecall?lat=' + lat + '&lon=' + lon + '&exclude=current,minutely,alerts&units=metric&appid=' + key;
  $.ajax({
      url: weather_url,
      dataType: 'json',
      type: 'GET',
    })
  .done(function (weather) {
    let insertHTML = '';
    for (let i = 0; i <= 1; i = i + 1) {
      insertHTML += buildHTML(weather, i);
    }
    $('#weather').html(insertHTML);
  })
  .fail(function (weather) {
    alert('天気予報の取得に失敗しました');
  });
});

// 日本語で表示
function buildHTML(weather, i) {
  //日付、時間を取得（Dateがミリ秒なので1000倍が必要）
  const date = new Date(weather.daily[i].dt * 1000);
  //UTCとの時差を無くす(日本は-9時間のため9を足す)
  date.setHours(date.getHours() + 9);
  //月を取得。getMonth()は0~11を返すため1を足すことによって1月~12月を返すように設定
  const month = date.getMonth() + 1;
  //曜日の日本語化のため、配列を用意する
  const Week = new Array('(日)', '(月)', '(火)', '(水)', '(木)', '(金)', '(土)');
  //月＋日＋曜日をdayに代入。getDay()は0~6を返すためWeek配列内のインデックスに対応した文字列を取得
  const day = month + '/' + date.getDate() + Week[date.getDay()];
  //天気のアイコンを取得
  const icon = weather.daily[i].weather[0].icon;
  //風速を取得
  const wind_speed = Math.floor(weather.hourly[i].wind_speed * 10) / 10;
  //風向（角度）を取得し方角表記へ変換
  const get_deg_string = function(wind_deg) {
    let r = '北';
    if (wind_deg>=11.25) r = '北北東';
    if (wind_deg>=33.75) r = '北東';
    if (wind_deg>=56.25) r = '東北東';
    if (wind_deg>=78.75) r = '東';
    if (wind_deg>=101.25) r = '東南東';
    if (wind_deg>=123.75) r = '南東';
    if (wind_deg>=146.25) r = '南南東';
    if (wind_deg>=168.75) r = '南';
    if (wind_deg>=191.25) r = '南南西';
    if (wind_deg>=213.75) r = '南西';
    if (wind_deg>=236.25) r = '西南西';
    if (wind_deg>=258.75) r = '西';
    if (wind_deg>=281.25) r = '西北西';
    if (wind_deg>=303.75) r = '北西';
    if (wind_deg>=326.25) r = '北北西';
    return r + '風';
  };
  const wind_deg = get_deg_string(weather.hourly[i].wind_deg);

  const html =
    '<div class="weather__content--report">' +
      '<img src="https://openweathermap.org/img/w/' + icon + '.png">' +
      '<p class="weather__content--report-date">' + day + "</p>" +
      '<p class="weather__content--report-wind_deg">' + wind_deg + "</p>" +
      '<p class="weather__content--report-wind_speed">' + wind_speed + "m</p>" +
    '</div>';
  return html
}
```


## 1. Rails変数を、JavaScriptで変数宣言 
```js:weather_api.js（1）
const lat = gon.spot.latitude
const lon = gon.spot.longitude
const key = gon.api
 // 天気予報を取得
const weather_url = 'https://api.openweathermap.org/data/2.5/onecall?lat=' + lat + '&lon=' + lon + '&exclude=current,minutely,alerts&units=metric&appid=' + key;
```
- 上3行
 - Railsの `controller内で定義した変数`を、JavaScriptでそれぞれ変数宣言
 - Railsで定義した変数を、JavaScriptで使うために、「 **[gon](https://github.com/gazay/gon)** 」 というgemを使用

- 4行目
 - Open Weather の **[One Call API](https://openweathermap.org/api/one-call-api)** の `url` を変数宣言し、後述のコードでAPIを呼び出し
 - url 内のexclude 等のオプションは **[OpenWeather](https://openweathermap.org/)** で詳細が見られます。


###gonの使い方
```r:Gemfile
gem 'gon'
```
```r:ターミナル
bundle install     #gem「gon」をインストール
```
```r:layouts/application.html.erb
<head>
  ... 省略 ...
  <%= include_gon %>     #gem「gon」を使えるよう設定
</head>
```
```rb:spots_controller.rb
def show
  @spot = Spot.find(params[:id])
  gon.spot = @spot
  gon.api = Rails.application.credentials.open_weather_api_key
end
```
これで、**controller内で定義した変数** `@spot`が、**JavaScript**では `gon.spot` として使えるようになりました。

- Rails「`@spot.latitude`」 ＝ JavaScript「`gon.spot.latitude`」

<img width="160" alt="spot-table.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646452/501b5755-3c79-4c1c-95ce-17097a45bc73.jpeg">

> [【gon】GitHub](https://github.com/gazay/gon)
[【Rails】gonっていうRailsで定義した変数をJSで使えるgem](https://makolog.xyz/gem/)


## 2. JSONデータ取得、データ加工等
```js:weather_api.js
$.ajax({
  url: weather_url,

     ...
     ...

//天気のアイコンを取得
  const icon = weather.daily[i].weather[0].icon;
```
この部分は、下記記事の解説が分かりやすかったです。
>[【週間天気予報表示】OpenWeatherMap APIを用いてユーザーが登録した住所から週間天気予報を表示する！（無料）](https://qiita.com/sdkk-rails/items/3589afefda4169976965)



## 3. 風速を取得
まず、Open Weather API からどのような情報（`JSONデータ`）が取得できているか確認するために、Chrome拡張の **[Advanced REST Client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=JA)** を使いました。


><img width="400" alt="api-check.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646452/27e29f2c-6326-0621-aaf7-deb014233197.jpeg">
path・オプション・APIキー等を入力し「SEND」ボタンを押すと、下記のようなJSONデータが表示されます。

.
><img width="400" alt="api-result.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/646452/268c1873-b892-258e-5b6d-2f230943cde2.jpeg">
このJSONデータから、必要な値をJSファイルに記述して取り出します。

```js:weather_api.js
//風速を取得
const wind_speed = Math.floor(weather.hourly[i].wind_speed * 10) / 10;
```

- `i番目のhourly配列` の `wind_speed` の値を取得
- その値の小数第二位を切り捨てる
- `wind_spped` という変数に入れる


## 4. 風向（角度）取得後、方角表記へ変換
```js:weather_api.js
//風向（角度）を取得、方角表記へ変換
const get_deg_string = function(wind_deg) {
  let r = '北';
  if (wind_deg>=11.25) r = '北北東';
  if (wind_deg>=33.75) r = '北東';
  if (wind_deg>=56.25) r = '東北東';
  if (wind_deg>=78.75) r = '東';
  if (wind_deg>=101.25) r = '東南東';
  if (wind_deg>=123.75) r = '南東';
  if (wind_deg>=146.25) r = '南南東';
  if (wind_deg>=168.75) r = '南';
  if (wind_deg>=191.25) r = '南南西';
  if (wind_deg>=213.75) r = '南西';
  if (wind_deg>=236.25) r = '西南西';
  if (wind_deg>=258.75) r = '西';
  if (wind_deg>=281.25) r = '西北西';
  if (wind_deg>=303.75) r = '北西';
  if (wind_deg>=326.25) r = '北北西';
  return r + '風';
};
const wind_deg = get_deg_string(weather.hourly[i].wind_deg);
```
- `i番目のhourly配列` の `wind_deg` の値を取得
- その値を、if文を使って方角に変換する
- `wind_deg` という変数に入れる

## 5. html生成
生成された `html` に、適宜cssでスタイルをあてて完成！🎉

#さいごに
**API** や **gem** を組み合わせる事で、より実用的なサービスにできたと思います。
間違っているところがあればご指摘いただけるとありがたいです。