【週間天気予報表示】OpenWeatherMap APIを用いてユーザーが登録した住所から週間天気予報を表示する！（無料）

# 概要
OpenWeatherMap APIを用いてユーザーが登録した住所から、週間天気予報を表示する機能を実装した時のことを備忘録として記録します。

# 環境
・ruby '2.5.7'
・rails '5.2.3'

# 前提
・OpenWeatherMapのAPIキーを取得済であること
・投稿モデル（ここではDatespotモデル）に都道府県名が登録されるカラムがあること
※筆者は、[jp_prefecture](https://github.com/chocoby/jp_prefecture)で都道府県名の登録ができるようにしています。

【参考】
・OpenWeatherMapのAPIキーを取得する手順
[【Rails】OpenWeatherMapを用いて、登録住所の天気予報を、日本語で表示する方法](https://qiita.com/matsubishi5/items/fcd77eacb0ed111299e2)

# 過程
## 0.完成形のコード
まずは、完成形のコードを示します。詳細を順に解説していきます。

```javascript:app/views/datespots/_weather.html.erb
<div class="weather">
  <h3 class="weather__title">天気予報</h3>
  <div id="weather"></div>
</div>
<script type="text/javascript">
$(function () {

  // 緯度経度を取得
  const latlon_url = 'https://api.openweathermap.org/geo/1.0/direct?q=<%= @datespot.prefecture.name %>&appid=<%= ENV['OPEN_WEATHER_MAP_API']%>';

  $.ajax({
    url: latlon_url,
    dataType: "json",
    type: 'GET',
  })
  .done(function (latlon) {
    const lat = latlon[0].lat
    const lon = latlon[0].lon

    // 天気予報を取得
    const weather_url = 'https://api.openweathermap.org/data/2.5/onecall?lat=' + lat + '&lon=' + lon + '&exclude=current,minutely,hourly,alerts&units=metric&appid=<%= ENV['OPEN_WEATHER_MAP_API']%>';

    $.ajax({
        url: weather_url,
        dataType: 'json',
        type: 'GET',
      })
      .done(function (weather) {
        let insertHTML = '';

        for (let i = 0; i <= 6; 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 html =
    '<div class="weather__content--report">' +
      '<img src="https://openweathermap.org/img/w/' + icon + '.png">' +
      '<span class="weather__content--report-date">' + day + "</span>" +
      '<div class="weather__content--report-temp-max">' + '最高：' + Math.round(weather.daily[i].temp.max) + "℃</div>" +
      '<span class="weather__content--report-temp-min">' + '最低：' + Math.floor(weather.daily[i].temp.min) + "℃</span>" +
    '</div>';
  return html
}
</script>
``` 

## 1.天気予報を表示するビューを作成
天気予報を表示するビューを作成します。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
<div class="weather">
  <h3 class="weather__title">天気予報</h3>
  <div id="weather"></div>
</div>
（省略）
``` 

ここでは、`<div id="weather"></div> `に天気予報を表示させます。
その他の`divタグ`、`hタグ`、`class名`は適宜設定してください。

## 2.緯度経度の取得
まずは、登録された都道府県名から緯度経度を取得します。
この時、[Geocoding API](https://openweathermap.org/api/geocoding-api)のDirect geocodingで都道府県名を緯度経度に変換します。
ちなみに、この[Geocoding API](https://openweathermap.org/api/geocoding-api)は無料で使用することができます。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
（省略）
<script type="text/javascript">
$(function () {

  // 緯度経度を取得
  const latlon_url = 'https://api.openweathermap.org/geo/1.0/direct?q=<%= @datespot.prefecture.name %>&appid=<%= ENV['OPEN_WEATHER_MAP_API']%>';

  $.ajax({
    url: latlon_url,
    dataType: "json",
    type: 'GET',
  })
  .done(function (latlon) {
    const lat = latlon[0].lat
    const lon = latlon[0].lon
（省略）
  })
});
</script>
``` 

まずは、以下のコードでAPI callをします。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
// 緯度経度を取得
const latlon_url = 'https://api.openweathermap.org/geo/1.0/direct?q=<%= @datespot.prefecture.name %>&appid=<%= ENV['OPEN_WEATHER_MAP_API']%>';
``` 
パラメータ`q`には、都市名を入れる必要がありますので、`<%= @datespot.prefecture.name %>`で都道府県名を入れています。

パラメータ`appid`には、APIキーを入れる必要がありますので、取得したAPIキーを入れてください。
ちなみに、取得したAPIキーは、不正利用を防ぐためにも環境変数に入れておきましょう。

そしたら、次はAjaxでAPIから情報を取得します。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
  $.ajax({
    url: latlon_url,
    dataType: "json",
    type: 'GET',
  })
  .done(function (latlon) {
    const lat = latlon[0].lat
    const lon = latlon[0].lon
  })
``` 

今回はjson形式で情報を取得するため、このように記述します。
`function(latlon)`としているため、取得した情報は引数`latlon`に入っています。
そこから、緯度と経度を取得したいので、緯度`latlon[0].lat`と経度`latlon[0].lon`で値を取得しています。

ちなみに、実際にどのような情報が取得できるのかを確認するためには、Chrome拡張の[Advanced REST Client](https://chrome.google.com/webstore/detail/advanced-rest-client/hgmloofddffdnphfgcellkdfbfbjeloo?hl=JA)が便利です。

[![Image from Gyazo](https://i.gyazo.com/883c6411618c5cbc1f0c2e1832477c3d.png)](https://gyazo.com/883c6411618c5cbc1f0c2e1832477c3d)

これを用いて、欲しい情報が取得できているか確認してみましょう。

## 3.週間天気予報の取得
取得した緯度経度から、週間天気予報を取得します。
[One Call API](https://openweathermap.org/api/one-call-api)に入っているDaily forecast for 7 daysから週間天気予報を取得します。
ちなみに、この[One Call API](https://openweathermap.org/api/one-call-api)も無料で使用することができます。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
$.ajax({
  url: latlon_url,
  dataType: "json",
  type: 'GET',
})
.done(function (latlon) {
  const lat = latlon[0].lat
  const lon = latlon[0].lon

  // 天気予報を取得
  const weather_url = 'https://api.openweathermap.org/data/2.5/onecall?lat=' + lat + '&lon=' + lon + '&exclude=current,minutely,hourly,alerts&units=metric&appid=<%= ENV['OPEN_WEATHER_MAP_API']%>';

  $.ajax({
      url: weather_url,
      dataType: 'json',
      type: 'GET',
    })
    .done(function (weather) {
      let insertHTML = '';

      for (let i = 0; i <= 6; i = i + 1) {
        insertHTML += buildHTML(weather, i);
      }
      $('#weather').html(insertHTML);
    })
    .fail(function (weather) {
      alert('天気予報の取得に失敗しました');
    });
})
``` 

まずは、以下のコードでAPI callをします。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
// 天気予報を取得
const weather_url = 'https://api.openweathermap.org/data/2.5/onecall?lat=' + lat + '&lon=' + lon + '&exclude=current,minutely,hourly,alerts&units=metric&appid=<%= ENV['OPEN_WEATHER_MAP_API']%>';
``` 
パラメータ`lat`,`lon`には、緯度・経度を入れる必要がありますので、先ほど取得した値を入れます。

パラメータ`exclude`は、API応答から気象データの一部を除外できます。ここでは、forecast for 7 days以外の情報は不要なので、除外の指定をしています。

パラメータ`units`は、このAPIの気温の単位はデフォルトだと華氏（°F）になっているため、`units=metric`を追記することで、摂氏（°C）に変更しています。

パラメータ`appid`には、同様に取得したAPIキーを入れてください。

そしたら、AjaxでAPIから情報を取得します。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
$.ajax({
  url: weather_url,
  dataType: 'json',
  type: 'GET',
})
.done(function (weather) {
  let insertHTML = '';

  for (let i = 0; i <= 6; i = i + 1) {
    insertHTML += buildHTML(weather, i);
  }
  $('#weather').html(insertHTML);
})
.fail(function (weather) {
  alert('天気予報の取得に失敗しました');
});
``` 

`配列daily`に7日間の天気予報の情報が順番に入っているため、`繰り返しfor`を使って`weather.daily[i]`とすることで順番にデータを取得できます。

あとは、「1.天気予報を表示するビューを作成」で用意した`<div id="weather"></div> `をjsの`htmlメソッド`で中身を書き換えることで天気予報を表示できます。

実際に表示させる部分については、次の項目で解説しています。

ちなみに、AjaxでAPIから情報を取得できなかった場合は、`天気予報の取得に失敗しました`という警告が表示されるようにしています。

## 4.週間天気予報の表示
最後に、取得したデータをもとに週間天気予報の表示させます。

```javascript:（抜粋）app/views/datespots/_weather.html.erb
// 日本語で表示
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 html =
    '<div class="weather__content--report">' +
      '<img src="https://openweathermap.org/img/w/' + icon + '.png">' +
      '<span class="weather__content--report-date">' + day + "</span>" +
      '<div class="weather__content--report-temp-max">' + '最高：' + Math.round(weather.daily[i].temp.max) + "℃</div>" +
      '<span class="weather__content--report-temp-min">' + '最低：' + Math.floor(weather.daily[i].temp.min) + "℃</span>" +
    '</div>';
  return html
}
``` 

このように、表示させる天気予報のHTMLは関数buildHTMLの中で組み立てています。

まずは、`weather.daily[i].dt`で、UNIX時間を取得しています。
JavaScriptの`Dateオブジェクト`はミリ秒なので、1000倍する必要があります。

また、`Dateオブジェクト`は協定世界時 (UTC) になっているので、`date.setHours(date.getHours() + 9);`で日本時間に直しています。

さらに、OpenWeatherMapでは、`http://openweathermap.org/img/w/04n.png`のような形式でアイコンが用意してあるので、これを利用してアイコンを表示しています。

こちらについても、Advanced REST Clientを参考にしながら欲しい情報を取得してみましょう。

# 結果
これで、週間天気予報が表示できました！
[![Image from Gyazo](https://i.gyazo.com/e6e67df63b25e1bac84e2a33383fcbd1.png)](https://gyazo.com/e6e67df63b25e1bac84e2a33383fcbd1)

# 参考
[【Rails/JS】無料API「OpenWeatherMap」で天気予報を表示する](https://qiita.com/mikan3rd/items/a5bc87a2242e67cf17a4)
[MDN Web Docs > Date](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date)