らいむぎばたけ

つかまえなくてだいじょうぶ

天気予報は前日との差分を知りたい

天気予報で今日が何度かを見ても「で、昨日より暑いの?寒いの?」となるので、差分を取って GAS で Slack に通知することにした。

やりたいこと

  • 今日の 12:00 頃の気温と前日の 12:00 頃の気温の差分が知りたい
    • 12:00 頃はごはんを食べた後のお散歩で一番外に出る可能性が高いから
  • 気温差分の通知は朝欲しい
    • 着替える前に知りたいから
  • 前日の気温は実績値でほしい

OpenWeatherMap の Free plan で事足りそうなのでこれを使うことにした。

openweathermap.org

取得したい値

OpenWeatherMap の使い方

OpenWeatherMap でアカウント登録して、API Key を作成して、取得したい地点の緯度経度が分かれば良い。緯度経度は自分で調べても良いが Geocoding API - OpenWeatherMap で調べることもできる。

秩父麦酒とイチローモルトが好きなので、緯度経度は西武秩父駅にした。

curl "https://api.openweathermap.org/data/2.5/weather?lat=35.9899&lon=139.0833&units=metric&appid={API_KEY}"
{
  "coord": {
    "lon": 139.0833,
    "lat": 35.9899
  },
  "weather": [
    {
      "id": 800,
      "main": "Clear",
      "description": "clear sky",
      "icon": "01n"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 1.17,
    "feels_like": -1.33,
    "temp_min": 0.86,
    "temp_max": 1.97,
    "pressure": 1026,
    "humidity": 38,
    "sea_level": 1026,
    "grnd_level": 996
  },
  "visibility": 10000,
  "wind": {
    "speed": 2.21,
    "deg": 254,
    "gust": 2.3
  },
  "clouds": {
    "all": 0
  },
  "dt": 1705414807,
  "sys": {
    "type": 2,
    "id": 73731,
    "country": "JP",
    "sunrise": 1705355605,
    "sunset": 1705391536
  },
  "timezone": 32400,
  "id": 1864637,
  "name": "Chichibu",
  "cod": 200
}

やったこと

// OpenWeatherMap の URL を生成する
function buildUrl(apiName) {
  const apiKey = PropertiesService.getScriptProperties().getProperty('OWM_API_KEY')
  const lat = 35.9899
  const lon = 139.0833
  return `https://api.openweathermap.org/data/2.5/${apiName}?lat=${lat}&lon=${lon}&units=metric&appid=${apiKey}`
}

// OpenWeatherMap の API を叩いてレスポンスを JSON で返す
function callApi(apiName) {
  const url = buildUrl(apiName)
  const response = UrlFetchApp.fetch(url)
  return JSON.parse(response)
}

// 今の気温 (実績値) をとってくる
function fetchCurrentTemp() {
  const json = callApi("weather")
  return json["main"]["temp"]
}

// 気温を保存しているセルを取得する
function getTempCell() {
  return SpreadsheetApp.getActiveSpreadsheet().getSheetByName("temp").getRange("A1")
}

// 今日の気温 (実績値) を保存する (cron で毎日 12:00 頃に実行する)
function saveTodayTemp() {
  getTempCell().setValue(`${fetchCurrentTemp()}`)
}

// 前日の実績気温をとってくる
function getYesterdayTemp() {
  return getTempCell().getValue()
}

// 当日 12:00 の予報の気温をとってくる
function fetchTodayForecastTemp() {
  const json = callApi("forecast")

  const today = new Date()
  const year = today.getFullYear()
  const month = today.getMonth() + 1
  const date = today.getDate() + 1

  const forecastUtcTime = Date.parse(`${year}-${month}-${date} 03:00:00 GMT`) / 1000

  const forecast = json["list"].find((item) => {
    return item["dt"] === forecastUtcTime
  })

  return forecast["main"]["temp"]
}

// 通知するメッセージを生成する
function buildMessage(todayForecastTemp, yesterdayTemp) {
  const tempDiff = Number.parseFloat(todayForecastTemp - yesterdayTemp).toFixed(1)
  const sign = (tempDiff > 0) ? '+' : ''

  return `今日は昨日と比べて ${sign}${tempDiff} 度だよ!`
}

// Slack に通知する
function sendSlack(message) {
  const token = PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN')

  UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", {
    method: "POST",
    payload: {
      token: token,
      channel: "#general",
      text: message,
      username: "name",
      icon_url: "https://example.com/icon.png",
    }
  })
}

// 気温差分を Slack に通知する (cron で毎日 08:00 頃に実行する)
function reportTempDiff() {
  const todayForecastTemp = fetchTodayForecastTemp()
  const yesterdayTemp = getYesterdayTemp()

  sendSlack(buildMessage(todayForecastTemp, yesterdayTemp))
}

あとは saveTodayTempreportTempDiff をトリガーに登録しておわり。

さむいね

OpenWeatherMap では体感温度の取得もできるので、もしかしたら体感温度のほうが良いかもしれない。そのうち考える。

なお、アプリの名前は天気予報ということでモネになりました。