朝は晴れていたけど、帰りに雨が降って困ったことはないですか?
出かけるときに天気予報で降水確率を確認しておけば、傘を持って出かけることもできますね。
でも、毎回天気を確認するのは少し大変ですよね。降水確率が高ければ自動でスマホに通知してくれたら便利じゃないですか?
今回はそんなアプリの作り方を紹介します。
アプリで使うのは「Google Apps Script」と「LINE Notify」の2つの無料サービスです。
Google Apps Script(GAS)は、Googleが提供するサービスの1つです。JavaScriptベースのスクリプト言語で、Googleアカウントを持っていれば無料で使えます。
LINE Notifyは、WEBサービスからの通知をLINEで受信できるようにするLINEのサービスです。
アプリの概要
朝6時~7時の間に、気象庁のサイトで夕方(12時~18時)の降水確率を確認し、降水確率が50%以上だったらLINEに通知します。
完成スクリプト
完成したスクリプトです。
const main = () => {
//気象庁からデータ取得
const forecastData = getForecastData();
//降水確率がない場合は終了
if(forecastData[2] == null){
return;
}
//降水確率が50%以下なら終了
const rainyPercent = 50;
if (parseInt(forecastData[2], 10) < rainyPercent) {
return;
}
//通知メッセージの作成
const time = forecastData[1].substr(0, 2);
const timeZone = `${time}-${parseInt(time, 10) + 6}`;
const message = `${forecastData[0]} ${timeZone}の降水確率は${forecastData[2]}%です。
傘持って行ったほうがいいよ`;
//LINEに送信
sendToLINE(message);
};
const getForecastData = () => {
//気象庁からデータ取得
const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json";
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response);
//欲しい地方の降水確率が格納されている配列の添字を取得
const areaName = "東京地方";
const areaIndex = data[0].timeSeries[1].areas.findIndex(areas => areas.area.name === areaName);
//欲しい時間の降水確率が格納されている配列の添字を取得
const today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd");
const time = "12:00";
const searchElement = today + "T" + time + ":00+09:00";
const popsIndex = data[0].timeSeries[1].timeDefines.indexOf(searchElement);
//降水確率を取得
const pops = data[0].timeSeries[1].areas[areaIndex].pops[popsIndex];
return [today, time, pops];
};
const sendToLINE = (message) => {
const token = "取得したアクセストークンを貼り付ける";
const options = {
"method": "post",
"payload": `message=${message}`,
"headers": {
"Authorization": `Bearer ${token}`
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
};
このスクリプトは、東京都の東京地方の降水確率を取得するスクリプトになっていますので、以下の箇所を変更して使ってください。
都道府県のURLやLINE Notifyのアクセストークンの取得方法は後ほど説明します。
そもそもGASの使い方がわからないって方は、次の記事を参考にしてください。
スクリプトエディタを開くところまでできればOKです。
気象庁から天気予報APIでデータを取得する
このアプリで一番大事なのは、降水確率のデータです。降水確率のデータがなければ始まりませんね。
でも、降水確率のデータは、どうやって取得すればいいのでしょう。
実は、気象庁からデータを取得することができます。
気象庁からデータを取得するために、天気予報APIを使います。
APIは、「Application Programming Interface」の略で、Wikipediaによると
アプリケーションプログラミングインタフェース(API、英: Application Programming Interface)とは、広義ではソフトウェアコンポーネント同士が互いに情報をやりとりするのに使用するインタフェースの仕様である。
ウィキペディア(Wikipedia)
簡単に言えば、あるアプリケーションが持っているデータを、外部から使えるように公開している仕様のことです。
気象庁から正式に公開されたAPIではないみたいですが、政府標準利用規約に準拠して使用することは可能とのこと。以下のサイトで詳しく書かれています。
気象庁公式の天気予報API(?)が発見 ~Twitterの開発者界隈に喜びの声が満ちる ~窓の社~
気象庁からデータを取得しているのが、以下のスクリプトです。
気象庁からデータを取得 ⇒ 必要なデータを取り出しといった処理を行っています。
const getForecastData = () => {
//気象庁からデータ取得
const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json";
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response);
//欲しい地方の降水確率が格納されている配列の添字を取得
const areaName = "東京地方";
const areaIndex = data[0].timeSeries[1].areas.findIndex(areas => areas.area.name === areaName);
//欲しい時間の降水確率が格納されている配列の添字を取得
const today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd");
const time = "12:00";
const searchElement = today + "T" + time + ":00+09:00";
const popsIndex = data[0].timeSeries[1].timeDefines.indexOf(searchElement);
//降水確率を取得
const pops = data[0].timeSeries[1].areas[areaIndex].pops[popsIndex];
return [today, time, pops];
};
詳しく見ていきましょう。
天気予報APIでデータを取得
天気予報APIを使って、気象庁からデータを取得しているのが以下のスクリプトです。
//気象庁からデータ取得
const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json";
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response);
1行目は確認したい都道府県ごとに代わります。
東京都の天気予報APIは”https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json”になります。
「forecast/130000.json」の130000という6桁の数字がエリアコードです。
この6桁の数字を、天気予報を取得したい都道府県のエリアコードに変更します。
「forecase/ここの数字6桁を書き換える.json」
東京都の場合は、130000なので、上のURLになります。
エリアコードは以下の方法で確認ができます。
例えば、千葉県の場合は、”https://www.jma.go.jp/bosai/forecast/data/forecast/120000.json”。
沖縄本島は “https://www.jma.go.jp/bosai/forecast/data/forecast/471000.json”になります。
外部APIを使うためのスクリプトが、2行目のUrlFetchApp.fetch(url)です。
fetchは、「取りに行く、とってくる、持ってくる」などの意味があります。
これで、天気予報APIを使って、天気予報データが取得できました。
3行目は、使いやすいようにJSONからJavaScriptオブジェクトに変換しています。
取得したデータを確認
取得したデータを確認してみましょう。
以下のスクリプトを実行すれば、ログでデータの確認ができます。
※東京都のデータになるので、必要であれば2行目のエリアコードを変更してください。
const checkLog = () => {
const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/130000.json";
const response = UrlFetchApp.fetch(url);
const data = JSON.parse(response);
Logger.log(data);
}
スクリプトを実行するには、実行したいを関数(checkLog)選択し、「実行」をクリックします。
実行ログがエディタの下部に表示されます。
初めて実行する場合は、承認が必要になります。
ログを確認してみます。そのままでは見にくいので、以下のサイトで見やすくしています。
[{
reportDatetime = 2021 - 09 - 22 T11: 00: 00 + 09: 00, publishingOffice = 気象庁,
timeSeries = [{
timeDefines = [2021 - 09 - 22 T11: 00: 00 + 09: 00, 2021 - 09 - 23 T00: 00:
00 + 09: 00, 2021 - 09 - 24 T00: 00: 00 + 09: 00
], areas = [{
weatherCodes = [200, 211, 101], waves = [0.5メートル 後 1 メートル, 1メートル 後 0.5 メートル, 0.5
メートル
], winds = [南の風 23 区西部 では 南の風 やや強く, 南の風 23 区西部 では 南の風 やや強く, 北の風 後 南の風],
weathers = [くもり 所により 雨 で 雷を伴う, くもり 昼前 から 晴れ 所により 明け方 まで 雨 で 雷を伴う,
晴れ 時々 くもり
], area = {
name = 東京地方, code = 130010
}
}, {
waves = [1.5メートル, 1.5メートル 後 2 メートル, 1.5メートル], winds = [南西の風 後 やや強く,
南西の風 やや強く, 西の風 後 北東の風
], weatherCodes = [202, 203, 101], area = {
code = 130020, name = 伊豆諸島北部
}, weathers = [くもり 昼過ぎ 一時 雨 所により 雷 を伴う, くもり 昼前 まで 時々 雨 後 晴れ 所により 昼前 まで 雷 を伴う,
晴れ 時々 くもり
]
}, {
winds = [南西の風 後 やや強く, 南西の風 やや強く 後 西の風, 西の風 後 北東の風], waves = [1.5
メートル, 1.5メートル 後 2 メートル, 1.5メートル
], weathers = [くもり 昼過ぎ 一時 雨 所により 雷 を伴う, くもり 昼前 まで 時々 雨 後 晴れ 所により 昼前 まで 雷 を伴う,
晴れ 時々 くもり
], area = {
name = 伊豆諸島南部, code = 130030
}, weatherCodes = [202, 203, 101]
}, {
waves = [1.5メートル, 1.5メートル, 1.5メートル 後 2 メートル], weatherCodes = [101, 101,
201
], winds = [南東の風, 南東の風 後 東の風, 東の風], weathers = [晴れ 時々 くもり, 晴れ 時々 くもり,
くもり 時々 晴れ
], area = {
code = 130040, name = 小笠原諸島
}
}]
}, {
timeDefines = [2021 - 09 - 22 T12: 00: 00 + 09: 00, 2021 - 09 - 22 T18: 00:
00 + 09: 00, 2021 - 09 - 23 T00: 00: 00 + 09: 00, 2021 - 09 - 23 T06: 00:
00 + 09: 00, 2021 - 09 - 23 T12: 00: 00 + 09: 00, 2021 - 09 - 23 T18: 00:
00 + 09: 00
], areas = [{
area = {
code = 130010, name = 東京地方
}, pops = [30, 20, 30, 20, 0, 10]
}, {
pops = [50, 30, 50, 50, 20, 10], area = {
name = 伊豆諸島北部, code = 130020
}
}, {
pops = [50, 30, 50, 50, 20, 20], area = {
name = 伊豆諸島南部, code = 130030
}
}, {
pops = [20, 20, 20, 10, 10, 10], area = {
name = 小笠原諸島, code = 130040
}
}]
}, {
timeDefines = [2021 - 09 - 22 T09: 00: 00 + 09: 00, 2021 - 09 - 22 T00: 00:
00 + 09: 00, 2021 - 09 - 23 T00: 00: 00 + 09: 00, 2021 - 09 - 23 T09: 00:
00 + 09: 00
], areas = [{
temps = [30, 30, 23, 31], area = {
code = 44132, name = 東京
}
}, {
area = {
code = 44172, name = 大島
}, temps = [28, 28, 23, 28]
}, {
area = {
code = 44263, name = 八丈島
}, temps = [28, 28, 24, 28]
}, {
temps = [31, 31, 27, 31], area = {
name = 父島, code = 44301
}
}]
}]
}, {
timeSeries = [{
timeDefines = [2021 - 09 - 23 T00: 00: 00 + 09: 00, 2021 - 09 - 24 T00: 00:
00 + 09: 00, 2021 - 09 - 25 T00: 00: 00 + 09: 00, 2021 - 09 - 26 T00: 00:
00 + 09: 00, 2021 - 09 - 27 T00: 00: 00 + 09: 00, 2021 - 09 - 28 T00: 00:
00 + 09: 00, 2021 - 09 - 29 T00: 00: 00 + 09: 00
], areas = [{
weatherCodes = [211, 101, 201, 200, 201, 201, 201], area = {
name = 東京地方, code = 130010
}, reliabilities = [, , B, B, A, A, A], pops = [, 20, 40, 30, 30, 30,
30
]
}, {
pops = [, 10, 30, 30, 30, 30, 30], reliabilities = [, , A, A, A, A, B],
weatherCodes = [203, 101, 201, 200, 201, 201, 201], area = {
name = 伊豆諸島, code = 130100
}
}, {
pops = [, 30, 40, 40, 50, 40, 40], reliabilities = [, , C, C, C, C, C],
weatherCodes = [101, 201, 200, 200, 202, 200, 200], area = {
code = 130040, name = 小笠原諸島
}
}]
}, {
timeDefines = [2021 - 09 - 23 T00: 00: 00 + 09: 00, 2021 - 09 - 24 T00: 00:
00 + 09: 00, 2021 - 09 - 25 T00: 00: 00 + 09: 00, 2021 - 09 - 26 T00: 00:
00 + 09: 00, 2021 - 09 - 27 T00: 00: 00 + 09: 00, 2021 - 09 - 28 T00: 00:
00 + 09: 00, 2021 - 09 - 29 T00: 00: 00 + 09: 00
], areas = [{
tempsMaxUpper = [, 31, 28, 28, 27, 28, 30], tempsMinUpper = [, 22, 20,
19, 18, 18, 20
], tempsMinLower = [, 18, 17, 15, 14, 14, 14], tempsMaxLower = [, 27,
24, 21, 21, 21, 23
], tempsMin = [, 20, 19, 18, 16, 16, 16], tempsMax = [, 29, 26, 24, 24,
25, 27
], area = {
name = 東京, code = 44132
}
}, {
area = {
name = 八丈島, code = 44263
}, tempsMaxLower = [, 27, 25, 23, 23, 24, 24], tempsMinLower = [, 21,
20, 19, 18, 18, 19
], tempsMax = [, 29, 27, 25, 25, 26, 26], tempsMaxUpper = [, 30, 28,
27, 26, 27, 28
], tempsMin = [, 22, 22, 21, 20, 20, 20], tempsMinUpper = [, 24, 23,
22, 22, 22, 22
]
}, {
tempsMin = [, 26, 26, 26, 25, 25, 25], tempsMinLower = [, 25, 25, 24,
23, 23, 24
], tempsMaxUpper = [, 32, 32, 31, 31, 31, 31], area = {
code = 44301, name = 父島
}, tempsMax = [, 31, 31, 30, 30, 30, 30], tempsMinUpper = [, 28, 27,
27, 26, 27, 28
], tempsMaxLower = [, 30, 30, 29, 28, 29, 28]
}]
}], reportDatetime = 2021 - 09 - 22 T11: 00: 00 + 09: 00, tempAverage = {
areas = [{
max = 25.3, area = {
code = 44132, name = 東京
}, min = 18.1
}, {
max = 26.2, min = 21.1, area = {
code = 44263, name = 八丈島
}
}, {
area = {
name = 父島, code = 44301
}, min = 25.5, max = 29.5
}]
}, publishingOffice = 気象庁, precipAverage = {
areas = [{
area = {
name = 東京, code = 44132
}, min = 21.7, max = 57.5
}, {
area = {
name = 八丈島, code = 44263
}, max = 108.6, min = 49.2
}, {
area = {
name = 父島, code = 44301
}, min = 8.7, max = 34.6
}]
}
}]
簡単にまとめると以下のような構造ですね。
・data[0]:明後日までの詳細の天気予報
timeSeries[0]:天気、風、波
timeSeries[1]:降水確率
timeSeries[2]:気温
・data[1]:7日先までの天気予報
timeSeries[0]:降水確率、信頼度
timeSeries[1]:気温
今日の降水確率が欲しいので、data[0]のtimeSeries[1]のデータを使います。
timeSeries[1]を見てます。※見やすいように少し加工しています。
{
timeDefines = [
2021 - 09 - 22 T12: 00: 00 + 09: 00,
2021 - 09 - 22 T18: 00: 00 + 09: 00,
2021 - 09 - 23 T00: 00: 00 + 09: 00,
2021 - 09 - 23 T06: 00: 00 + 09: 00,
2021 - 09 - 23 T12: 00: 00 + 09: 00,
2021 - 09 - 23 T18: 00: 00 + 09: 00
],
areas = [
{
area = {code = 130010, name = 東京地方},
pops = [30, 20, 30, 20, 0, 10]
},
{
area = {code = 130020, name = 伊豆諸島北部},
pops = [50, 30, 50, 50, 20, 10]
},
{
area = {code = 130030, name = 伊豆諸島南部},
pops = [50, 30, 50, 50, 20, 20]
},
{
area = {code = 130040, name = 小笠原諸島},
pops = [20, 20, 20, 10, 10, 10]
}
]
}
timeDefinesとareasのデータがあり、areasは更に地方(area)と降水確率(pops)のデータを持っています。
このデータから、欲しい地方の降水確率を取得する必要があります。
まずは、地方のデータが配列の何番目にあるのかを調べます。
それが以下のスクリプトです。
//欲しい地方の降水確率が格納されている配列の添字を取得
const areaName = "東京地方";
const areaIndex = data[0].timeSeries[1].areas.findIndex(areas => areas.area.name === areaName);
2行目には全国の天気予報~気象庁~にアクセスし、都道府県を選択した後に表示される地方名を入力してください。
data[0].timeSeries[1].areasの配列の何番目に欲しいデータが格納されているのかを調べるのがfindIndex()です。
これは、配列内の指定した内容を満たす最初の要素の位置を返します。
const areaIndex = data[0].timeSeries[1].areas.findIndex(areas => areas.area.name === areaName);
変数areaIndexは、areas配列内のnameとareaNameが同じ要素が格納されている添字(配列の何番目の要素か)を取得しています。
これで、areasの何番目に欲しい地名のデータが格納されているかわかりました。
次に、降水確率のデータを取得します。
降水確率はpopsに格納されていますが、timeDefinesの時間に対応した順番で格納されています。
以下のコードでは、pops[0]の降水確率は、timeDefines[0]の2021年9月22日12時からの降水確率になります。
※以下のコードは見やすいように、不要なところは消しています。
{
timeDefines = [
2021 - 09 - 22 T12: 00: 00 + 09: 00,
2021 - 09 - 22 T18: 00: 00 + 09: 00,
2021 - 09 - 23 T00: 00: 00 + 09: 00,
2021 - 09 - 23 T06: 00: 00 + 09: 00,
2021 - 09 - 23 T12: 00: 00 + 09: 00,
2021 - 09 - 23 T18: 00: 00 + 09: 00
],
areas = [
{
area = {code = 130010, name = 東京地方},
pops = [30, 20, 30, 20, 0, 10]
}
]
}
12時から18時までの降水確率が欲しいので、その時間が配列の何番目に格納されているのか調べる必要があります。
12時から18時がtimeDifinesの何番目に格納されているか検索するのが、以下のスクリプトです。
//欲しい時間の降水確率が格納されている配列の添字を取得
const today = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd");
const time = "12:00";
const searchElement = today + "T" + time + ":00+09:00";
const popsIndex = data[0].timeSeries[1].timeDefines.indexOf(searchElement);
2行目のUtilities.formatDate(date, timeZone, format)は、今日の日付のDateオブジェクトを文字列に変換しています。
3行目は、必要な時間帯の開始時間になります。気象庁のデータは、6時間ごとになるので「00:00」、「06:00」、「12:00」、「18:00」のいずれかになります。
4行目で検索する文字列を作成しています。
5行目のindexOf(searchElement)は、配列の中から検索したい文字が格納されている添字(配列の何番目の要素か)を返します。
これで、12時から18時の降水確率が配列の何番目に格納されているかがわかりました。
あとは、降水確率を取得して、必要なデータを返すだけです。それが以下のスクリプトです。
//降水確率を取得
const pops = data[0].timeSeries[1].areas[areaIndex].pops[popsIndex];
return [today, time, pops];
LINEに送信するメッセージを作成する
LINEに送信するメッセージを作成して、LINEに送信します。それが以下のスクリプトです。
const main = () => {
//気象庁からデータ取得
const forecastData = getForecastData();
//降水確率がない場合は終了
if(forecastData[2] == null){
return;
}
//降水確率が50%以下なら終了
const rainyPercent = 50;
if (parseInt(forecastData[2], 10) < rainyPercent) {
return;
}
//通知メッセージの作成
const time = forecastData[1].substr(0, 2);
const timeZone = `${time}-${parseInt(time, 10) + 6}`;
const message = `${forecastData[0]} ${timeZone}の降水確率は${forecastData[2]}%です。
傘持って行ったほうがいいよ`;
//LINEに送信
sendToLINE(message);
};
3行目で、先ほど解説したgetForecastData()から降水確率などのデータを配列として受け取ります。
気象庁からデータを取得するスクリプトを別の関数式に書いたことで、main()がスッキリします。
forecastData[2]には降水確率が格納されています。
降水確率のデータがない場合は、処理しても意味がないので終了します。
//降水確率がない場合は終了
if(forecastData[2] == null){
return;
}
対象外の条件のときに早期returnすることで、コードをスッキリさせて可読性が上がります。
このようなif文のことをガード節といいます。
降水確率が50%以上の場合に通知したいので、50%より小さい場合も処理を終了します。
//降水確率が50%以下なら終了
const rainyPercent = 50;
if (parseInt(forecastData[2], 10) < rainyPercent) {
return;
}
forecastDataに格納されているデータは、文字列型になります。
このままだと比較できないので、文字列型からint型に変換します。それが、parseInt(string[, radix])です。
string部には変換したい文字列、radix部には10進数に変換したいので10を指定します。
メッセージの作成をしているのが以下のスクリプトです。message変数に、送信するメッセージを格納しています。
//通知メッセージの作成
const time = forecastData[1].substr(0, 2);
const timeZone = `${time}-${parseInt(time, 10) + 6}`;
const message = `${forecastData[0]} ${timeZone}の降水確率は${forecastData[2]}%です。
傘持って行ったほうがいいよ`;
forecastData[1]には「12:00」という文字列が入っています。
ここから12だけを取り出すために、substr(start, length)を使います。substr(0, 2)とすることで、「12:00」の左から2文字を取得できます。
3~5行目で、バッククォーテーション(`)で囲まれた文字列をテンプレートリテラルといいます。
テンプレートリテラルは、改行したいところで改行コード「/n」などを書く必要がなく、改行したいところでEnterを入力するだけで改行できます。
変数は「${変数}」と書きます。
作成したメッセージをsendToLINEに渡すことでLINEに送信します。
//LINEに送信
sendToLINE(message);
LINEに送信する
LINEに送信するスクリプトがsendToLINE()です。
const sendToLINE = (message) => {
const token = "取得したアクセストークンを貼り付ける";
const options = {
"method": "post",
"payload": `message=${message}`,
"headers": {
"Authorization": `Bearer ${token}`
}
};
UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
};
引数として受け取った文字列をLINEに送信することができます。
2行目には、取得したアクセストークンを書きます。
LINEに通知するために「アクセストークン」を発行する
LINEに通知するためにLINE Notifyを使用しますが、そのためには「アクセストークン」が必要になります。
LINEのマイページにアクセスし、トークンを発行します。そのトークンを先ほどのスクリプトの2行目に貼り付けてください。
トークンの発行方法は、以下の記事を参考にしてください。
自動で実行する
これで気象庁からデータを取得し、LINEに送信するスクリプトができました。
「main」を実行することで、降水確率を取得し、LINEにデータを送信できます。
あとは、「main」を自動で実行させるだけですね。
GASのトリガーで毎日実行する
GASには、指定した日時に関数を自動で実行させる「トリガー」という機能があります。
このトリガーを使うことで、毎日自動で「main」を実行させることができます。
トリガーを使うために、スクリプトエディタで「トリガーを追加」をクリックします。
実行する関数は「main」、特定の時間に動作させたいので「時間主導型」を選択します。
時間ベースのトリガーのタイプは「日付ベースのタイマー」、時刻は「午前6時~7時」を選択し、「保存」をクリックします。
これで毎日午前6時~7時の間に「main」が実行されます。
まとめ
天気予報のデータは、気象庁からAPIを利用することで取得することができます。
LINE Notifyを使用することで任意のメッセージを送信することができます。
GASを決まった時間に実行するためには「トリガー」を使用します。
GASとLINE Notifyを使えばいろいろなアプリを作ることができます。