【GAS】GoogleAppsScriptを使って気象庁のAPIから天気予報を取得してみる

# はじめに
皆様ご機嫌よう、黄瀬のん（Xenon_ura118）です。

せっかくの冬休みですので、プログラミングの練習を兼ねてタイトル通りのことをやってみました。
※作成にあたり、以下の記事を大変参考にさせていただきました。ありがとうございます！
https://qiita.com/nishifeoda/items/7e458b261111f201c724

# 作成物の概要
以下のスプレッドシートにて、気象庁の天気予報APIから情報を取得し、整形・表示しています。
使い方は書いてある通りです。
![forecast.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/b779d81b-398d-cb88-11de-48087981e117.png)

ちなみに、①の地域選択については、同ファイル内の別シートに一覧を記載しておき、プルダウンで選択できるようにしています。
※参考にさせていただいた記事内にも記載がありますが、気象庁の天気予報APIを取得するときに「予報区コード」という情報が必要になります。（だいたい都府県単位で分かれているコードですが、北海道と沖縄だけ特殊です。）
　予報区コード一覧は以下の記事を参考にさせていただきました。（記事内で「※取得不可」と記載されているコードは除いています）
 　https://anko.education/webapi/jma

![code.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/00cf23b4-5c4b-0eeb-0ac2-66bb433cc35e.png)

# スクリプトの内容

「天気を表示」ボタンに割り当てているスクリプトは以下の通りとなります。
```forecast.gs
function forecast() {
  //スプレッドシートの定義
  const ss = SpreadsheetApp.getActiveSpreadsheet()
  const sheet = ss.getSheetByName("ツール")
  
  //予報区コード読み取り、URLにセット
  const code = sheet.getRange("B2").getValue()
  const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"+code+".json"

  //HTTP APIを呼び出してJSONを取得
  const responce = UrlFetchApp.fetch(url)
  const data = JSON.parse(responce.getContentText())

  //予報発表時間（読み取り）
  let reporttimeRead = data[0].reportDatetime
  //予報発表時間（整形）
  let reporttime = reporttimeRead.slice(11,16)

  //予報区内の代表都市を取得
  let city = data[0].timeSeries[2].areas[0].area.name

  //今日の日付（読み取り）
  let todayRead = data[0].timeSeries[0].timeDefines[0]
  //今日の日付（整形）
  let today = todayRead.slice(0,10)
  //今日の天気
  let todayForecast = data[0].timeSeries[0].areas[0].weathers[0]
  //今日の最低気温
  let todayMinTemps = "-"
  //今日の最高気温
  let todayMaxTemps = "-"
  //予報発表時間によって最高気温を設定
  if(reporttime != "17:00") {
    todayMaxTemps = data[0].timeSeries[2].areas[0].temps[1]
  }
  //今日の結果を配列に格納する
  let todayArray = [[today, todayForecast, todayMinTemps, todayMaxTemps]]

  //明日の日付（読み取り）
  let tomorrowRead = data[0].timeSeries[0].timeDefines[1]
  //明日の日付（整形）
  let tomorrow = tomorrowRead.slice(0,10)
  //明日の天気
  let tomorrowForecast = data[0].timeSeries[0].areas[0].weathers[1]
  //明日の最低気温
  let tomorrowMinTemps = "-"
  //明日の最高気温
  let tomorrowMaxTemps = "-"
  //予報発表時間によって気温を設定
  if(reporttime == "17:00") {
    tomorrowMinTemps = data[0].timeSeries[2].areas[0].temps[0]
    tomorrowMaxTemps = data[0].timeSeries[2].areas[0].temps[1]
  }else {
    tomorrowMinTemps = data[0].timeSeries[2].areas[0].temps[2]
    tomorrowMaxTemps = data[0].timeSeries[2].areas[0].temps[3]
  }

  //明日の結果を配列に格納する
  let tomorrowArray = [[tomorrow, tomorrowForecast, tomorrowMinTemps, tomorrowMaxTemps]]

  //スプレッドシートに書き出し
  sheet.getRange("A13").setValue(city)
  sheet.getRange(15, 1, 1, 4).setValues(todayArray)
  sheet.getRange(16, 1, 1, 4).setValues(tomorrowArray)

}
```

各箇所について説明していきます。

①気象庁のAPIから情報を取得する部分
```
  //予報区コード読み取り、URLにセット
  const code = sheet.getRange("B2").getValue()
  const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/"+code+".json"

  //HTTP APIを呼び出してJSONを取得
  const responce = UrlFetchApp.fetch(url)
  const data = JSON.parse(responce.getContentText())
```
予報区コードはプルダウンから取得するとB2セルへ表示できるようにしているので、それを読み取ってAPI呼び出し用のURLにセットしています。
例で出している宗谷地方だと、以下のURLになります。
https://www.jma.go.jp/bosai/forecast/data/forecast/011000.json

APIから呼び出した情報はJSON形式です。
`//HTTP APIを呼び出してJSONを取得`の部分で、読み取った内容を処理できるように変換しています。

なお、JSONと同じ内容が可視化されていると思われる天気予報のページは以下の通りです。
このページとJSONを照合すると、データ構造を把握しやすいかと思います。
https://www.jma.go.jp/bosai/forecast/#area_type=offices&area_code=011000

②予報発表時間読み取りの部分
```
  //予報発表時間（読み取り）
  let reporttimeRead = data[0].reportDatetime
  //予報発表時間（整形）
  let reporttime = reporttimeRead.slice(11,16)
```

このAPI内の情報は毎日5時、11時、17時に発表されています。
後ほどの処理でこの「予報発表時間」が必要になってくるので、読み取っています。
ちなみに、読み取ったままでは「2024-12-30T17:00:00+09:00」のような形式になっているので、
`slice`メソッドで予報発表時間のみを切り取っています。

③予報区内の代表都市を取得する部分
```
  let city = data[0].timeSeries[2].areas[0].area.name
```
こちらは、可視化されたページにて「気温」の欄に表示されている代表都市を取得しています。
宗谷地方だと「稚内」しかないのですが、東京都のように都市が複数表示されている場合は、一番上に表示されている代表都市を取得しています。

④今日の天気予報を取得する部分
```
  //今日の日付（読み取り）
  let todayRead = data[0].timeSeries[0].timeDefines[0]
  //今日の日付（整形）
  let today = todayRead.slice(0,10)
  //今日の天気

  let todayForecast = data[0].timeSeries[0].areas[0].weathers[0]
  //今日の最低気温
  let todayMinTemps = "-"
  //今日の最高気温
  let todayMaxTemps = "-"
  //予報発表時間によって最高気温を設定
  if(reporttime != "17:00") {
    todayMaxTemps = data[0].timeSeries[2].areas[0].temps[1]
  }
  //今日の結果を配列に格納する
  let todayArray = [[today, todayForecast, todayMinTemps, todayMaxTemps]]
```
今日の日付についても、読み取った段階では「②予報発表時間読み取りの部分」で紹介したものと同じ形式になっているので、
`slice`メソッドで年月日のみを切り取っています。

`//予報発表時間によって最高気温を設定`の部分が少し厄介です。
気象庁のHPを見ると、予報発表時間によって今日の気温は以下の通りに設定されています。
■5時、11時
最低気温はハイフン（JSON上では最高気温と最低気温に同じ値がセットされている）
![5時.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/3c78dd6c-61af-3284-c68d-648f4fcc1135.png)
![11時.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/ebab3e32-d4c9-25a7-5198-af82952b9210.png)
■17時
最低気温、最高気温ともにハイフン（JSON上では最低気温、最高気温ともに値のセットなし）
![17時.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3818350/e2ac85ed-a4c2-118b-0c79-1990fdd5ad14.png)


よって、予報発表時間が5時、11時の場合のみ、JSONから最高気温を読み取って変数にセットするコードを記載しています。
だから事前に予報発表時間を取得する必要があったんですね。

変数`todayArray`には、スプレッドシートに書き出す都合上、結果を二次元配列で格納しています。

⑤明日の天気予報を取得する部分
```
  //明日の日付（読み取り）
  let tomorrowRead = data[0].timeSeries[0].timeDefines[1]
  //明日の日付（整形）。、
  let tomorrow = tomorrowRead.slice(0,10)
  //明日の天気
  let tomorrowForecast = data[0].timeSeries[0].areas[0].weathers[1]
  //明日の最低気温
  let tomorrowMinTemps = "-"
  //明日の最高気温
  let tomorrowMaxTemps = "-"
  //予報発表時間によって気温を設定
  if(reporttime == "17:00") {
    tomorrowMinTemps = data[0].timeSeries[2].areas[0].temps[0]
    tomorrowMaxTemps = data[0].timeSeries[2].areas[0].temps[1]
  }else {
    tomorrowMinTemps = data[0].timeSeries[2].areas[0].temps[2]
    tomorrowMaxTemps = data[0].timeSeries[2].areas[0].temps[3]
  }

  //明日の結果を配列に格納する
  let tomorrowArray = [[tomorrow, tomorrowForecast, tomorrowMinTemps, tomorrowMaxTemps]]
  ```
今日の天気予報を取得する処理とほとんど同じなので、特に言うことはないです。
予報発表時間によって最低気温、最高気温の取得元を変更しています。

⑥スプレッドシートに書き出す部分
```
//スプレッドシートに書き出し
  sheet.getRange("A13").setValue(city)
  sheet.getRange(15, 1, 1, 4).setValues(todayArray)
  sheet.getRange(16, 1, 1, 4).setValues(tomorrowArray)
```

こちらも特に言うことはないです。
`setValues`メソッドを使用して一括で書き出すために、二次元配列で準備しておく必要があったんですね。

# おわりに
GASでAPIから情報を取得することはおろか、JSON形式のデータを扱うこと自体が初めてだったので、よい勉強になりました。
記事内に誤りがありましたら、コメントでご指摘いただけるとありがたいです。

以上です。お読みいただきありがとうございました！

# 余談
例で稚内の天気を扱っておりました。
予報区コードの先頭にあるというのも1つの理由なのですが、わたしが好きな場所だからというのも理由です。
人生で一度は宗谷岬に行ってみてほしいと思っています。景色もよいですし、何よりも「最北端に来た！」という達成感を味わうことができます。あと稚内市中心部→宗谷岬への道も景色が最高です！ドライブがてら楽しんで行くことができるかと思います。
中心部から近いノシャップ岬もオススメです。駅からバスで10分くらいで気軽に行ける場所です。（しかも、バスの本数が結構多い！）
岬近くにある水族館も気になっていたのですが、しばらく休館しているようなので、営業再開したら行ってみたいです。

以上、~~本当にどうでもいい~~余談でした。