18.Javascriptで作る週間天気予報プログラム



では早速、Javascriptでも天気予報表示プログラムを作成してみましょう‼️

18-1.週間天気予報のURL

週間天気予報は府県天気予報と同じファイルに入っていますので、URLは府県天気予報と同じです。

・三重県の週間天気予報を取得する

https://www.jma.go.jp/bosai/forecast/data/forecast/240000.json


18-2.週間天気予報の発表時刻

週間天気予報の発表時刻内容は、

①発表時刻 11時、17時の一日二回となります。
②発表内容 向こう一週間の、各府県における一日ごとの天気
      最高・最低気温(1℃単位)
      降水確率(10%単位)
      予報の信頼度
      期間における降水量(1㎜単位)と気温の平年値(0.1℃単位)

※気象庁ホームページでは、最新の「府県天気予報」から、その府県予報区の代表的な一次細分区域の明日・明後日の予報を随時取り込んで掲載しています。

三重県で例えると、北中部の今日明日の天気予報を週間天気予報に取り込んでいます。


18-3.週間天気予報のデータ仕様

三重県のデータを参考に見てみます。

今回は、天気コードと最高最低気温データのみの取得になります。

①11・17時発表のデータ仕様

17時発表のものを参考にしていますが、11時発表のものと内容は同じです。

天気コードweatherCodesは、[0]~[6]まで、7日分存在します。

最低気温データは、tempsMinのものを使います。

tempsMinUppeは、最大値の最低気温データです。

tempsMinLowerは、最小値の最低気温データです。

最高気温データは、tempsMaxのものを使います。

tempsMaxUpperは、最大値の最高気温データです。

tempsMaxLowerは、最小値の最高気温データです。


18-4.Javascriptで週間天気予報の処理プログラム

Javascriptの処理プログラムは、下記になります。

const weatherCode = {
  100: ["100.svg", "500.svg", "晴れ"],
  101: ["101.svg", "501.svg", "晴れ時々曇り"],
  102: ["102.svg", "502.svg", "晴れ一時雨"],
  103: ["102.svg", "502.svg", "晴れ時々雨"],
  104: ["104.svg", "504.svg", "晴れ一時雪"],
  105: ["104.svg", "504.svg", "晴れ時々雪"],
  106: ["102.svg", "502.svg", "晴れ一時雨か雪"],
  107: ["102.svg", "502.svg", "晴れ時々雨か雪"],
  108: ["102.svg", "502.svg", "晴れ一時雨か雷雨"],
  110: ["110.svg", "510.svg", "晴れ後時々曇り"],
  111: ["110.svg", "510.svg", "晴れ後曇り"],
  112: ["112.svg", "512.svg", "晴れ後一時雨"],
  113: ["112.svg", "512.svg", "晴れ後時々雨"],
  114: ["112.svg", "512.svg", "晴れ後雨"],
  115: ["115.svg", "515.svg", "晴れ後一時雪"],
  116: ["115.svg", "515.svg", "晴れ後時々雪"],
  117: ["115.svg", "515.svg", "晴れ後雪"],
  118: ["112.svg", "512.svg", "晴れ後雨か雪"],
  119: ["112.svg", "512.svg", "晴れ後雨か雷雨"],
  120: ["102.svg", "502.svg", "晴れ朝夕一時雨"],
  121: ["102.svg", "502.svg", "晴れ朝の内一時雨"],
  122: ["112.svg", "512.svg", "晴れ夕方一時雨"],
  123: ["100.svg", "500.svg", "晴れ山沿い雷雨"],
  124: ["100.svg", "500.svg", "晴れ山沿い雪"],
  125: ["112.svg", "512.svg", "晴れ午後は雷雨"],
  126: ["112.svg", "512.svg", "晴れ昼頃から雨"],
  127: ["112.svg", "512.svg", "晴れ夕方から雨"],
  128: ["112.svg", "512.svg", "晴れ夜は雨"],
  130: ["100.svg", "500.svg", "朝の内霧後晴れ"],
  131: ["100.svg", "500.svg", "晴れ明け方霧"],
  132: ["101.svg", "501.svg", "晴れ朝夕曇り"],
  140: ["102.svg", "502.svg", "晴れ時々雨と雷"],
  160: ["104.svg", "504.svg", "晴れ一時雪か雨"],
  170: ["104.svg", "504.svg", "晴れ時々雪か雨"],
  181: ["115.svg", "515.svg", "晴れ後雪か雨"],
  200: ["200.svg", "200.svg", "曇り"],
  201: ["201.svg", "601.svg", "曇り時々晴れ"],
  202: ["202.svg", "202.svg", "曇り一時雨"],
  203: ["202.svg", "202.svg", "曇り時々雨"],
  204: ["204.svg", "204.svg", "曇り一時雪"],
  205: ["204.svg", "204.svg", "曇り時々雪"],
  206: ["202.svg", "202.svg", "曇り一時雨か雪"],
  207: ["202.svg", "202.svg", "曇り時々雨か雪"],
  208: ["202.svg", "202.svg", "曇り一時雨か雷雨"],
  209: ["200.svg", "200.svg", "霧"],
  210: ["210.svg", "610.svg", "曇り後時々晴れ"],
  211: ["210.svg", "610.svg", "曇り後晴れ"],
  212: ["212.svg", "212.svg", "曇り後一時雨"],
  213: ["212.svg", "212.svg", "曇り後時々雨"],
  214: ["212.svg", "212.svg", "曇り後雨"],
  215: ["215.svg", "215.svg", "曇り後一時雪"],
  216: ["215.svg", "215.svg", "曇り後時々雪"],
  217: ["215.svg", "215.svg", "曇り後雪"],
  218: ["212.svg", "212.svg", "曇り後雨か雪"],
  219: ["212.svg", "212.svg", "曇り後雨か雷雨"],
  220: ["202.svg", "202.svg", "曇り朝夕一時雨"],
  221: ["202.svg", "202.svg", "曇り朝の内一時雨"],
  222: ["212.svg", "212.svg", "曇り夕方一時雨"],
  223: ["201.svg", "601.svg", "曇り日中時々晴れ"],
  224: ["212.svg", "212.svg", "曇り昼頃から雨"],
  225: ["212.svg", "212.svg", "曇り夕方から雨"],
  226: ["212.svg", "212.svg", "曇り夜は雨"],
  228: ["215.svg", "215.svg", "曇り昼頃から雪"],
  229: ["215.svg", "215.svg", "曇り夕方から雪"],
  230: ["215.svg", "215.svg", "曇り夜は雪"],
  231: ["200.svg", "200.svg", "曇り海岸霧か霧雨"],
  240: ["202.svg", "202.svg", "曇り時々雨と雷"],
  250: ["204.svg", "204.svg", "曇り時々雪と雷"],
  260: ["204.svg", "204.svg", "曇り一時雪か雨"],
  270: ["204.svg", "204.svg", "曇り時々雪か雨"],
  281: ["215.svg", "215.svg", "曇り後雪か雨"],
  300: ["300.svg", "300.svg", "雨"],
  301: ["301.svg", "701.svg", "雨時々晴れ"],
  302: ["302.svg", "302.svg", "雨時々止む"],
  303: ["303.svg", "303.svg", "雨時々雪"],
  304: ["300.svg", "300.svg", "雨か雪"],
  306: ["300.svg", "300.svg", "大雨"],
  308: ["308.svg", "308.svg", "雨で暴風を伴う"],
  309: ["303.svg", "303.svg", "雨一時雪"],
  311: ["311.svg", "711.svg", "雨後晴れ"],
  313: ["313.svg", "313.svg", "雨後曇り"],
  314: ["314.svg", "314.svg", "雨後時々雪"],
  315: ["314.svg", "314.svg", "雨後雪"],
  316: ["311.svg", "711.svg", "雨か雪後晴れ"],
  317: ["313.svg", "313.svg", "雨か雪後曇り"],
  320: ["311.svg", "711.svg", "朝の内雨後晴れ"],
  321: ["313.svg", "313.svg", "朝の内雨後曇り"],
  322: ["303.svg", "303.svg", "雨朝晩一時雪"],
  323: ["311.svg", "711.svg", "雨昼頃から晴れ"],
  324: ["311.svg", "711.svg", "雨夕方から晴れ"],
  325: ["311.svg", "711.svg", "雨夜は晴れ"],
  326: ["314.svg", "314.svg", "雨夕方から雪"],
  327: ["314.svg", "314.svg", "雨夜は雪"],
  328: ["300.svg", "300.svg", "雨一時強く降る"],
  329: ["300.svg", "300.svg", "雨一時みぞれ"],
  340: ["400.svg", "400.svg", "雪か雨"],
  350: ["300.svg", "300.svg", "雨で雷を伴う"],
  361: ["411.svg", "811.svg", "雪か雨後晴れ"],
  371: ["413.svg", "413.svg", "雪か雨後曇り"],
  400: ["400.svg", "400.svg", "雪"],
  401: ["401.svg", "801.svg", "雪時々晴れ"],
  402: ["402.svg", "402.svg", "雪時々止む"],
  403: ["403.svg", "403.svg", "雪時々雨"],
  405: ["400.svg", "400.svg", "大雪"],
  406: ["406.svg", "406.svg", "風雪強い"],
  407: ["406.svg", "406.svg", "暴風雪"],
  409: ["403.svg", "403.svg", "雪一時雨"],
  411: ["411.svg", "811.svg", "雪後晴れ"],
  413: ["413.svg", "413.svg", "雪後曇り"],
  414: ["414.svg", "414.svg", "雪後雨"],
  420: ["411.svg", "811.svg", "朝の内雪後晴れ"],
  421: ["413.svg", "413.svg", "朝の内雪後曇り"],
  422: ["414.svg", "414.svg", "雪昼頃から雨"],
  423: ["414.svg", "414.svg", "雪夕方から雨"],
  425: ["400.svg", "400.svg", "雪一時強く降る"],
  426: ["400.svg", "400.svg", "雪後みぞれ"],
  427: ["400.svg", "400.svg", "雪一時みぞれ"],
  450: ["400.svg", "400.svg", "雪で雷を伴う"],
};

// ここのXXXXXX.jsonを変更する
const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/240000.json";

const dayList = ["日", "月", "火", "水", "木", "金", "土"];

const timeDefinesList = new Array();
const weatherCodeList = new Array();
const tempsMinList = new Array();
const tempsMaxList = new Array();

fetch(url)
  .then(function (response) {
    return response.json();
  })
  .then(function (weather) {
    document
      .getElementById("location")
      .prepend(
        `${weather[1].publishingOffice}: ${weather[1].timeSeries[0].areas[0].area.name} `
      );
    const isTodaysData = weather[0].timeSeries[2].timeDefines.length === 4;
    const weatherCodes = weather[0].timeSeries[0].areas[0].weatherCodes;
    const timeDefines = weather[0].timeSeries[0].timeDefines;
    const temps = weather[0].timeSeries[2].areas[0].temps;
    weatherCodeList.push(weatherCodes[0], weatherCodes[1]);
    timeDefinesList.push(timeDefines[0], timeDefines[1]);
    if (isTodaysData) {
      tempsMinList.push(temps[0] === temps[1] ? "--" : temps[0], temps[2]);
      tempsMaxList.push(temps[1], temps[3]);
    } else {
      tempsMinList.push("--", temps[0]);
      tempsMaxList.push("--", temps[1]);
    }

    const startCount =
      weather[1].timeSeries[0].timeDefines.indexOf(timeDefines[1]) + 1;
    for (let i = startCount; i < startCount + 5; i++) {
      weatherCodeList.push(weather[1].timeSeries[0].areas[0].weatherCodes[i]);
      timeDefinesList.push(weather[1].timeSeries[0].timeDefines[i]);
      tempsMinList.push(weather[1].timeSeries[1].areas[0].tempsMin[i]);
      tempsMaxList.push(weather[1].timeSeries[1].areas[0].tempsMax[i]);
    }

    const date = document.getElementsByClassName("date");
    const weatherImg = document.getElementsByClassName("weatherImg");
    const weatherTelop = document.getElementsByClassName("weatherTelop");
    const tempMin = document.getElementsByClassName("tempMin");
    const tempMax = document.getElementsByClassName("tempMax");

    weatherCodeList.forEach(function (el, i) {
      let dt = new Date(timeDefinesList[i]);
      let weekdayCount = dt.getDay();
      if (weekdayCount === 0) date[i].style.color = "red";
      if (weekdayCount === 6) date[i].style.color = "blue";
      var m = ("00" + (dt.getMonth() + 1)).slice(-2);
      var d = ("00" + dt.getDate()).slice(-2);
      date[i].textContent = `${m}/${d}(${dayList[weekdayCount]})`;
      var isNight = Number(i === 0 && !isTodaysData)
      weatherImg[i].src = "https://www.jma.go.jp/bosai/forecast/img/" + weatherCode[el][isNight];
      weatherTelop[i].textContent = weatherCode[el][2];
      tempMin[i].textContent = tempsMinList[i] + "℃";
      tempMax[i].textContent = tempsMaxList[i] + "℃";
    });
  });

① weatherCode オブジェクトの役割

const weatherCode = {
100: ["100.svg", "500.svg", "晴れ"],
...
};

何をしている?

気象庁の 天気コード(数値) を、

  • 昼用アイコン
  • 夜用アイコン
  • 日本語テロップ

に変換するための 対応表(辞書) です。

weatherCode[100]
// → ["100.svg", "500.svg", "晴れ"]

つまり後半で

weatherCode[el][0 or 1] // アイコン
weatherCode[el][2] // 天気文言

として使うためのものです。

💡
[0] = 昼
[1] = 夜
という構造にしているのがポイント。


② 気象庁JSONのURL

const url = "https://www.jma.go.jp/bosai/forecast/data/forecast/240000.json";
  • 240000府県予報区コード
  • ここを変えるだけで地域が切り替わる

👉 かなり「再利用性の高い」設計です。


③ 曜日表示用配列

const dayList = ["日", "月", "火", "水", "木", "金", "土"];

Date.getDay() の戻り値(0〜6)を
日本語曜日に変換するための配列。


④ データ格納用の配列

const timeDefinesList = new Array();
const weatherCodeList = new Array();
const tempsMinList = new Array();
const tempsMaxList = new Array();

なぜ配列に貯める?

  • 今日+明日+週間5日
    一旦まとめてから
  • 最後に forEach で一気に描画するため

👉 DOM操作を分離しているのが綺麗。


⑤ fetch でJSON取得

fetch(url)
.then(response => response.json())
.then(weather => {

ここで weather
気象庁の forecast JSON を丸ごと持った配列


⑥ 地点名・発表官署の表示

document.getElementById("location").prepend(
`${weather[1].publishingOffice}: ${weather[1].timeSeries[0].areas[0].area.name}`
);
  • weather[1]週間予報ブロック
  • publishingOffice → 発表官署
  • area.name → 地域名

⑦ 今日データかどうかの判定(超重要)

const isTodaysData = weather[0].timeSeries[2].timeDefines.length === 4;

何を判定している?

  • 今日の予報が含まれる場合 → length === 4
  • 含まれない場合 → length === 2

これは 気象庁JSON最大の地雷ポイント

👉 この判定があるから
最低・最高気温のズレ問題を回避できています。


⑧ 今日・明日のデータ取得

const weatherCodes = weather[0].timeSeries[0].areas[0].weatherCodes;
const timeDefines = weather[0].timeSeries[0].timeDefines;
const temps = weather[0].timeSeries[2].areas[0].temps;
  • weather[0]今日・明日
  • timeSeries[0] → 天気
  • timeSeries[2] → 気温

⑨ 今日・明日分を配列に追加

weatherCodeList.push(weatherCodes[0], weatherCodes[1]);
timeDefinesList.push(timeDefines[0], timeDefines[1]);

気温の扱い(分岐が神)

if (isTodaysData) {
tempsMinList.push(
temps[0] === temps[1] ? "--" : temps[0],
temps[2]
);
tempsMaxList.push(temps[1], temps[3]);
} else {
tempsMinList.push("--", temps[0]);
tempsMaxList.push("--", temps[1]);
}
  • 今日の最低気温が「既に出終わっている」場合は "--"
  • JSONの構造差をきちんと吸収している

👉 実運用レベルの書き方です。


⑩ 週間5日分の開始位置を計算

const startCount =
weather[1].timeSeries[0].timeDefines.indexOf(timeDefines[1]) + 1;

何をしている?

  • 明日の日付が週間予報のどこにあるかを探す
  • その次の日から5日分を表示

👉 今日・明日と週間の 重複問題を完全回避


⑪ 週間5日分をループで追加

for (let i = startCount; i < startCount + 5; i++) {
weatherCodeList.push(...);
timeDefinesList.push(...);
tempsMinList.push(...);
tempsMaxList.push(...);
}

これで
合計7日分 のデータが揃います。


⑫ DOM取得

const date = document.getElementsByClassName("date");
const weatherImg = document.getElementsByClassName("weatherImg");
...

HTML側に

<div class="date"></div>
<img class="weatherImg">

のような要素が 7個ずつ 並んでいる前提。


⑬ 描画処理(forEach)

weatherCodeList.forEach(function (el, i) {

日付と曜日

let dt = new Date(timeDefinesList[i]);
let weekdayCount = dt.getDay();
  • 日曜 → 赤
  • 土曜 → 青
date[i].textContent = `${m}/${d}(${dayList[weekdayCount]})`;

⑭ 昼夜アイコンの切り替え(地味に高度)

var isNight = Number(i === 0 && !isTodaysData)
  • 今日が含まれない場合
  • 最初の予報は「今夜」

👉 だから夜アイコンを使う、という判断。

weatherImg[i].src =
"https://www.jma.go.jp/bosai/forecast/img/" +
weatherCode[el][isNight];

⑮ 天気テロップと気温

weatherTelop[i].textContent = weatherCode[el][2];
tempMin[i].textContent = tempsMinList[i] + "℃";
tempMax[i].textContent = tempsMaxList[i] + "℃";

18-5.HTMLの週間天気予報表示プログラム

<link rel="stylesheet" href="./weather.css">
<div id="location"><a href="https://www.jma.go.jp/bosai/forecast/" target="_blank">気象庁のデータを元に作成</a></div>
<div class="weatherForecast">
  <div class="weather">
    <div class="date">--/--(-)</div>
    <img class="weatherImg">
    <div class="weatherTelop">--</div>
    <div><span class="tempMin">-℃</span>/<span class="tempMax">-℃</span></div>
  </div>
</div>
<script>
  for (let i = 0; i < 6; i++) {
    const el = document.querySelector('.weather').cloneNode(true);
    document.querySelector('.weatherForecast').appendChild(el);
  }
</script>
<script src="./weather.js"></script>

① CSS の読み込み

<link rel="stylesheet" href="./weather.css">
  • 天気予報全体のレイアウト・色・フォント調整用
  • JavaScriptとは独立していて
    👉 表示専用(見た目) を担当

② location(地点・出典表示)

<div id="location">
<a href="https://www.jma.go.jp/bosai/forecast/" target="_blank">
気象庁のデータを元に作成
</a>
</div>

役割

  • JavaScript側で
document.getElementById("location").prepend(...)

される 地点名・発表官署の表示エリア

ポイント

  • <a> を入れておくことで
    気象庁データ利用の明示(利用規約的にも◎)
  • target="_blank" で別タブ表示

③ 天気予報全体のコンテナ

<div class="weatherForecast">
  • 7日分の天気カードをまとめる親要素
  • JS側で
document.querySelector('.weatherForecast').appendChild(el);

の追加先になる


④ 天気1日分の「ひな型」

<div class="weather">

ここが超重要。

この .weather
「1日分の天気予報カードのテンプレート」 です。


④-1 日付表示

<div class="date">--/--(-)</div>
  • 初期値はダミー
  • JSで
date[i].textContent = "mm/dd(曜)";

に上書きされる


④-2 天気アイコン

<img class="weatherImg">
  • src は最初は空
  • JSで
weatherImg[i].src = "https://www.jma.go.jp/..."

をセット

👉 <img> を先に置いておくのが正解


④-3 天気テロップ

<div class="weatherTelop">--</div>
  • 「晴れ」「曇り時々雨」など
  • weatherCode[el][2] がここに入る

④-4 最低/最高気温

<div>
<span class="tempMin">-℃</span>/<span class="tempMax">-℃</span>
</div>
  • 最低・最高を spanで分離
  • CSSで色分け(最低=青、最高=赤)がしやすい

JS側では

tempMin[i].textContent = tempsMinList[i] + "℃";
tempMax[i].textContent = tempsMaxList[i] + "℃";

⑤ weatherカードを6個複製するスクリプト

<script>
for (let i = 0; i < 6; i++) {
const el = document.querySelector('.weather').cloneNode(true);
document.querySelector('.weatherForecast').appendChild(el);
}
</script>

何をしている?

  • 最初にHTMLに書いた .weather1個だけ
  • それを 6回複製
  • 合計 7日分 にする

cloneNode(true) の意味

  • true中身(子要素)も含めて丸ごとコピー
  • false だと箱だけになる(今回は不可)

💡
HTMLを7個手書きしないで済む
→ メンテ性が高い、賢い方法。


⑥ weather.js の読み込み(最後が重要)

<script src="./weather.js"></script>

なぜ一番下?

  • DOM(HTML)がすべて生成されてから
  • JSが .date, .weatherImg を取得できるようにするため

👉 defer を使わず、順序で安全を確保している書き方。


全体の流れ(超要約)

  1. HTMLで 天気1日分のテンプレート を1個作る
  2. JavaScriptで 6個複製 → 7日分
  3. weather.js が
    • 日付
    • 天気アイコン
    • 天気テロップ
    • 気温
      を上書き
  4. CSSで見た目を整える

18-6.週間天気予報の表示CSS

.weatherForecast {
  width: 840px;
  height: 120px;
  display: flex;
  text-align: center;
  border: 1px solid #000;
}

.weather {
  width: calc(100% / 7);
  height: 100%;
  box-sizing: border-box;
  border: 1px solid #666;
}

.weather > div {
  height: 20%;
  font-size: 14px;
}

.weatherImg {
  height: 42%;
}

.tempMin {
  color: blue;
}

.tempMax {
  color: red;
}

🌤 全体像

このCSSは:

[ 1日目 ][ 2日目 ][ 3日目 ][ 4日目 ][ 5日目 ][ 6日目 ][ 7日目 ]

という 横並びレイアウト を作っています。


.weatherForecast

.weatherForecast {
width: 840px;
height: 120px;
display: flex;
text-align: center;
border: 1px solid #000;
}

役割

👉 天気カード全体の入れ物


各プロパティの意味

width: 840px;

横幅を固定(840px)


height: 120px;

高さを120pxに固定


display: flex; ⭐超重要

これが一番大事です。

👉 子要素を横並びにする

つまり .weather が横に並びます。


text-align: center;

中の文字を中央揃え


border: 1px solid #000;

全体を黒枠で囲む


.weather

.weather {
width: calc(100% / 7);
height: 100%;
box-sizing: border-box;
border: 1px solid #666;
}

役割

👉 1日分の天気カード


各プロパティ

width: calc(100% / 7);

👉 全体を 7等分

例:

840px ÷ 7 = 120px

つまり1カード = 120px


height: 100%;

親(120px)いっぱいの高さ


box-sizing: border-box; ⭐重要

通常は:

width = 中身だけ
border は別に加算される

でも border-box にすると:

width の中に border を含める

👉 レイアウト崩れ防止


border: 1px solid #666;

各カードを区切る線


.weather > div

.weather > div {
height: 20%;
font-size: 14px;
}

意味

👉 .weather の直下の div 全部

あなたのHTMLだと:

日付
テロップ
気温表示

など。


height: 20%;

.weather の高さは 120px

120px × 20% = 24px

👉 各ブロックを均等に縦分割


font-size: 14px;

文字サイズ指定


.weatherImg

.weatherImg {
height: 42%;
}

意味

👉 天気アイコンの高さを

120px × 42% ≒ 50px

に設定

画像を少し大きめにしています。


⑤ 気温の色分け

.tempMin {
color: blue;
}.tempMax {
color: red;
}

👉

  • 最低気温 → 青
  • 最高気温 → 赤

日本の天気予報の定番カラーですね。


最後までお読みくださり、本当にありがとうございます。

PHPを理解したあなたは、Javascriptを理解することで、更に飛躍することでしょう‼️

是非、Javascriptでの天気予報表示プログラムにも、挑戦してください‼️

分からないことは、ChatGPTに聞いて下さいね‼️😊🤖

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA