PHPで暑さ指数予測を地図上に表示する

(1/1)
2025年(令和7年)の夏は異常な暑さが続いており、熱中症警戒アラートが出続けている。環境省の熱中症予防情報サイトでは、暑さ指数電子情報提供サービスにおいて、暑さ指数(WBGT)予測値などを提供している。
今回は、住所やランドマークをキーにして場所を検索し、その付近の暑さ指数予測を地図上に色づけ表示をするPHPプログラムを作ってみることにする。加えて、3時間毎3日分の予測データが得られることから、スライダーを使って任意の日時の暑さ指数予測をマッピングする。

(2025年8月17日).pahooEnv導入

目次

サンプル・プログラム:実行例

PHPで暑さ指数予測を地図上に表示する
Googleマップ表示

サンプル・プログラムのダウンロード

圧縮ファイルの内容
envWBGTforecast.phpサンプル・プログラム本体
envWBGTinfoInit.php予測地点情報ファイルを作成するプログラム
envWBGTinfoSpots.xml直近の予測地点情報ファイル(XML形式)
envWBGTinfoSpots.csv直近の予測地点情報ファイル(CSV形式)
pahooWeather.php気象情報に関わるクラス pahooWeather。
気象情報に関わるクラスの使い方は「PHPで天気予報を求める(その3)」「PHPで暑さ指数予測を地図上に表示する」などを参照。include_path が通ったディレクトリに配置すること。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
envWBGTforecast.php 更新履歴
バージョン 更新日 内容
1.1.0 2025/08/17 .pahooEnv導入
1.0.0 2025/08/09 初版
envWBGTinfoInit.php 更新履歴
バージョン 更新日 内容
1.1.0 2025/08/17 .pahooEnv導入
1.0.0 2025/08/10 初版
pahooWeather.php 更新履歴
バージョン 更新日 内容
5.7.0 2025/08/09 readEnvWBGTinfoSpots(), readEnvWBGTforecast(), getWBGTcolor() 追加
5.6.2 2025/04/10 readJmaSpots() -- bug-fix
5.6.1 2025/04/08 getMyscriptPathURL() -- bug-fix
5.6.0 2025/02/23 getJmaNearSpot() -- 引数 $distanceMax 追加
5.5.0 2025/02/01 予報地点情報ファイルを1週間毎に再作成する
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.8.0 2025/08/10 アクセスキーなどを ".env" に分離
6.7.1 2025/07/26 jsLine_Gmap() - bug-fix
6.7.0 2025/07/20 drawJSmap,drawGMap -- 引数 $markerLevel 追加
6.6.0 2025/07/19 drawJSmap,drawGMap,drawLeaflet -- マップ中心マーカー表示引数を追加
6.5.0 2025/06/14 GoogleMaps JavaScript APIの変更に対応
pahooCache.php 更新履歴
バージョン 更新日 内容
1.1.3 2025/08/10 var→public
1.1.2 2023/07/22 bug-fix
1.1.1 2023/02/11 コメント追記
1.1 2021/04/08 simplexml_load()メソッド追加
1.0 2021/04/02 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooGeoCode クラス

pahooGeoCode.php

  40: class pahooGeoCode {
  41:     public $items;      // 検索結果格納用
  42:     public $error;      // エラー・フラグ
  43:     public $errmsg;     // エラー・メッセージ
  44:     public $hits;       // 検索ヒット件数
  45:     public $webapi// 直前に呼び出したWebAPI URL
  46: 
  47:     // -- 以下のデータは .env ファイルに記述可能
  48:     // Google Cloud Platform APIキー
  49:     // https://cloud.google.com/maps-platform/
  50:     // ※Google Maps APIを利用しないのなら登録不要
  51:     public $GOOGLE_API_KEY_1 = '';      // HTTPリファラ用
  52:     public $GOOGLE_API_KEY_2 = '';      // IP制限用
  53:     public $GOOGLE_MAP_ID    = '';      // GoogleMaps ID
  54: 
  55:     // Yahoo! JAPAN Webサービス アプリケーションID
  56:     // https://e.developer.yahoo.co.jp/register
  57:     // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  58:     public $YAHOO_APPLICATION_ID = '';
  59: 
  60:     // OSM Nominatim Search API利用時に知らせるメールアドレス
  61:     // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
  62:     // ※OSM Nominatim Search APIを利用しないのなら登録不要
  63:     public $NOMINATIM_EMAIL = '';
  64: 
  65:     // IP2Location.io APIキー
  66:     // https://www.ip2location.io/
  67:     // ※IP2Location.ioを利用しないのなら登録不要
  68:     public $IP2LOCATION_API_KEY = '';

GoogleマップやLeafletなどによる地図描画や住所検索を行うためのクラスが pahooGeoCode である。同梱のクラス・ファイル "pahooGeoCode.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooGeoCodeクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

地図や住所検索として Google を利用するのであれば Google Cloud Platform APIキーマップID が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、IP2Location.ioを利用するのであれば「PHPでIPアドレスやホスト名から住所を求める」を、それぞれ参照されたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooCache クラス

pahooCache.php

  13: class pahooCache {
  14:     const LIFE_CACHE = (2 * 60);        // キャッシュ保持時間(デフォルト;分)
  15:     const DEF_DIRCACHE = './pcache/';   // キャッシュ・ディレクトリ(デフォルト)
  16: 
  17:     public $lifeCache;      // キャッシュ保持時間(分)(0:キャッシュしない)
  18:     public $dirCache;       // キャッシュ用ディレクトリ
  19:     public $error;          // エラーフラグ
  20:     public $errmsg;         // エラーメッセージ
  21:     public $debug;          // デバッグ用ファイル名
  22: 
  23: /**
  24:  * コンストラクタ
  25:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  26:  * @param   int    $life キャッシュ保持時間(分)(省略可能)
  27:  * @param   string $dir キャッシュ・ディレクトリ(省略可能)
  28:  * @return  なし
  29: */
  30: function __construct($life=self::LIFE_CACHE, $dir=self::DEF_DIRCACHE) {
  31:     if ($life < 0) {
  32:         $life = 0;
  33:     }
  34:     if (preg_match('/\/$/ui', $dir) == 0) {
  35:         $dir = $dir . '/';
  36:     }
  37:     $this->error      = FALSE;
  38:     $this->errmsg     = '';
  39:     $this->debug      = '';
  40:     $this->lifeCache = $life;
  41:     $this->dirCache  = $dir;
  42: 
  43:     // PHP5以上であることを調べる.
  44:     if (! $this->isphp5over()) {
  45:         $this->error   = TRUE;
  46:         $this->errmsg  = '動作にはPHP5以上が必要です';
  47:         return;
  48:     }
  49: 
  50:     // キャッシュ・ディレクトリが無ければ作成する.
  51:     if (! is_dir($this->dirCache)) {
  52:         $res = mkdir($this->dirCache, 0744);
  53:         if ($res == FALSE) {
  54:             $this->error   = TRUE;
  55:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
  56:             return;
  57:         }
  58:     }
  59: }

クラウド連携や相手先サイトの公開データを利用する際、こちらのアプリを起動する都度、APIを呼び出したりデータをダウンロードするのでは、相手サーバに負荷をかけてしまう。
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが pahooCacheクラス である。同梱のクラス・ファイル "pahooCache.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooCacheクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

pahooCacheクラス の注意ポイントは、キャッシュ時間(単位:分)とキャッシュを保存するディレクトリをコンストラクタで指定している点だ。これらはプログラムによって変わるものである。インスタンス化するときの値は、pahooCacheクラス を利用するメイン・プログラムの方で解説する。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

準備:暑さ指数(WBGT)予測値データファイル

環境省は、暑さ指数電子情報提供サービスにおいて、暑さ指数予測値データを CSV形式ファイルで提供している。入手にあたって、とくにアカウント登録したり、APIキーを取得する必要はない。
このページの最下段に、「利用規約確認」ボタンがあるので、それをクリックすると、説明書(PDF形式)をダウンロードできる。CSV形式ファイルの種類、公開場所、データ形式は説明書に記載されている。
ここでは、「環境省の暑さ指数(WBGT)予測値等電子情報提供サービスについて」の「全地点の予測値データファイル」を利用する。
また、「当サイトのご利用にあたって」を熟読してから、データ利用をはじめてほしい。

準備:予測地点情報ファイル

前述の「全地点の予測値データファイル」のCSV形式ファイルは、1地点=1行になっており、全国800地点以上の予測値データが格納されている。ここで、行を識別するのが地点番号だが、これだけでは、具体的にどの住所のデータかが分からない。

説明書(PDF形式ファイル)の【別表1】に、地点番号と、観測所名、所在地(住所)の一覧表が載っている。この【別表1】をCSV形式ファイルなどで公開しているか探したのだが見つからなかった。次善の策として、PDF形式ファイルを Adobe Acrobatを使ってExcel形式に変換してからCSV形式ファイル形式ファイルに保存した。これが同梱の "envWBGTinfoSpots.csv" である。Adobe公式の PDFをCSVに変換する方法を使うと、無償でオンライン変換できることを確認している。

解説:予測地点情報ファイルを作成する

"envWBGTinfoSpots.csv" が用意できたら、次に、これを利用しやすいよう、下記のような構造をもった XML形式ファイルに変換する。
予測地点情報ファイル(xml) envWBGTinfospots date 作成日付 version バージン番号 spot code 地点番号 region 地方 promotionBureau 振興局 observatory 観測所名 address 所在地 latitude 緯度 longitude 経度 distance1 1番近い観測所までの距離(メートル) distance2 2番目に近い観測所までの距離(メートル) distance3 3番目に近い観測所までの距離(メートル) spot code 地点番号 region 地方 promotionBureau 振興局 observatory 観測所名 address 所在地 latitude 緯度 longitude 経度 distance1 1番近い観測所までの距離(メートル) distance2 2番目に近い観測所までの距離(メートル) distance3 3番目に近い観測所までの距離(メートル)
タグ <spot> で囲まれたデータが1つの観測地点の情報である。
緯度、経度は地図にマッピングするために必要であるから、後述するプログラムによってGoogleCloud API を使って取得する。緯度、経度が分かったら、観測所間の距離を近い順に3つ計算している。これは。警報エリアとして、地図上に六角形を描いて警報色で塗りつぶすのだが、その六角形の大きさを指定するのに使う。

envWBGTinfoInit.php

  63: // 初期値(START) =============================================================
  64: 
  65: // 暑さ指数(WBGT)情報提供地点一覧
  66: // 『熱中症特別警戒情報及び熱中症警戒情報のデータ提供形式』の【別表1】をCSV化したファイル
  67: // https://www.wbgt.env.go.jp/data_service.php
  68: define('FILE_WBGTINFOCSV', 'envWBGTinfoSpots.csv');
  69: // このファイルのエンコード
  70: define('FILE_WBGTINFOCSV_ENCODE', 'SJIS');
  71: // CSVセパレーター
  72: define('FILE_WBGTINFOCSV_SEPARATOR', "\t");
  73: 
  74: // 表示幅(単位:ピクセル)
  75: define('WIDTH',  600);
  76: 
  77: // 地方名【変更不可】
  78: // データレコード識別のために使用する.
  79: $TableLocal = array(
  80: '北海道', '東北', '関東', '甲信', '東海', '北陸',
  81: '近畿', '中国', '四国', '九州', '沖縄'
  82: );
  83: 
  84: // キャッシュ保持時間(分) 0:キャッシュしない ※本プログラムでは使用しない
  85: // 気象庁へのアクセス負荷軽減のため,60分以上のキャッシュ保持をお勧めします.
  86: define('LIFE_CACHE', 120);
  87: 
  88: // キャッシュ・ディレクトリ ※本プログラムでは使用しない
  89: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
  90: // 最大150Mバイトを消費します.
  91: define('DIR_CACHE', './pcache/');
  92: 
  93: // 初期値(END) ===============================================================

初期値は、とくに「変更不可」の記載がないものは自由に変更できる。

envWBGTinfoInit.php

 174: /**
 175:  * 暑さ指数(WBGT)情報提供地点一覧のデータを配列に格納する.
 176:  * @param   string $csvfile   暑さ指数(WBGT)情報提供地点一覧ファイル名(CSV)
 177:  * @param   array  $infoSpots データ格納用配列
 178:  * @param   string $errmsg    エラーメッセージ格納用
 179:  * @return  int 格納データ数/FALSE:失敗
 180: */
 181: function getWEBGTinfoSpots($csvfile, &$infoSpots, &$errmsg) {
 182:     global $TableLocal;
 183: 
 184:     // インスタンス生成
 185:     $pgo = new pahooGeoCode();
 186: 
 187:     // 読み込みオープン
 188:     $infile = fopen($csvfile, 'r');
 189:     if ($infile === FALSE) {
 190:         $errmsg = $csvfile . ' が見つからないか,開くことができません';
 191:         return FALSE;
 192:     }
 193:     // 読み込み中の行番号
 194:     $lineNum = 0;
 195:     // 格納データ数
 196:     $cnt = 0;
 197: 
 198:     // 1行ずつ読み込んで解析する.
 199:     while (! feof($infile)) {
 200:         $lineNum++;
 201:         $line = fgets($infile);
 202:         if (($line === FALSE|| ($line === ''))    continue;
 203: 
 204:         $line = mb_convert_encoding($line, INTERNAL_ENCODING, FILE_WBGTINFOCSV_ENCODE);
 205:         $flag = FALSE;
 206:         // 冒頭に地方名がある行をデータ行として採用する.
 207:         foreach ($TableLocal as $prefix) {
 208:             // 先頭一致
 209:             if (strpos($line, $prefix) === 0) {
 210:                 $flag = TRUE;
 211:                 break;
 212:             }
 213:         }
 214:         // 先頭が一致しなければ次の行へ
 215:         if (! $flag)    continue;
 216: 
 217:         // 1行分のデータを配列に格納する
 218:         if (getWEBGTinfoSpot($line, $lineNum, $infoSpots, $errmsg) == FALSE) {
 219:             return FALSE;
 220:         }
 221:         $cnt++;
 222:     }
 223: 
 224:     // 観測地点の緯度・経度を代入する.
 225:     foreach ($infoSpots as $code=>$infoSpot) {
 226:         $items = array();
 227:         $arr = preg_split('/[\s ]+/ui', $infoSpot['address']);
 228:         $n = $pgo->getPointsV3_all($arr[0], $items);
 229:         if ($n <0) {
 230:             $errmsg = $lineNum . '行目の住所から緯度・経度を求めることができません';
 231:             $infoSpots[$code]['latitude']  = '';
 232:             $infoSpots[$code]['longitude'] = '';
 233:         } else {
 234:             $infoSpots[$code]['latitude']  = $items[1]['latitude'];
 235:             $infoSpots[$code]['longitude'] = $items[1]['longitude'];
 236:         }
 237:     }
 238: 
 239:     // 観測地点に隣接する観測地点までの距離を近い順に3つ代入する.
 240:     foreach ($infoSpots as $code0=>$infoSpot0) {
 241:         $distansList = array();
 242:         foreach ($infoSpots as $code1=>$infoSpot1) {
 243:             if (is_numeric($infoSpot0['longitude']) && is_numeric($infoSpot1['longitude'])) {
 244:                 $distansList[] = $pgo->distance($infoSpot0['longitude'], $infoSpot0['latitude'], $infoSpot1['longitude'], $infoSpot1['latitude']);
 245:             } else {
 246:                 $distansList[] = 9999999;
 247:             }
 248:         }
 249:         sort($distansList);
 250:         $infoSpots[$code0]['distance1'] = (int)$distansList[1];
 251:         $infoSpots[$code0]['distance2'] = (int)$distansList[2];
 252:         $infoSpots[$code0]['distance3'] = (int)$distansList[3];
 253:     }
 254: 
 255:     // インスタンス解放
 256:     $pgo = NULL;
 257: 
 258:     return $cnt;
 259: }

まず、CSV形式ファイルを1行ずつ読み込み、配列に格納するユーザー定義関数が getWEBGTinfoSpots である。
冒頭に地方名がある行をデータ行として判定する仕組みになっているため。同じ地方名画記述されている【別表1】の最後にある観測終了した地点のデータは削除してから CSV形式ファイルを作っておくこと。

観測地点の緯度、経度は、pahoGeoCodeクラスgetPointsV3_all メソッドを利用し、GoogleCloud API を使って取得する。
観測所間の距離は、pahoGeoCodeクラスdistance メソッドを利用し、他の観測所との距離を総当たりで計算し、 sort 関数を使って近い順に並べ替えた後、上位3地点のデータを取り込む。

envWBGTinfoInit.php

 261: /**
 262:  * 暑さ指数(WBGT)情報提供地点一覧の1つを配列に格納する.
 263:  * @param   string $line      1行分のデータ
 264:  * @param   int    $lineNum   行番号
 265:  * @param   array  $infoSpots データ格納用配列
 266:  * @param   string $errmsg    エラーメッセージ格納用
 267:  * @return  TRUE:格納成功/FALSE:失敗
 268: */
 269: function getWEBGTinfoSpot($line, $lineNum, &$infoSpots, &$errmsg) {
 270:     $arr = preg_split('/' . FILE_WBGTINFOCSV_SEPARATOR . '/ui', $line);
 271:     $cnt = 1;
 272:     foreach ($arr as $str) {
 273:         if ($str !== '') {
 274:             switch ($cnt) {
 275:                 // 地方
 276:                 case 1:
 277:                     $region = trim($str);
 278:                     $cnt++;
 279:                     break;
 280:                 // 振興局
 281:                 case 2:
 282:                     $promotionBureau = trim($str);
 283:                     $cnt++;
 284:                     break;
 285:                 // 地点番号
 286:                 case 3:
 287:                     $code = trim($str);
 288:                     if (! preg_match('/^[0-9]+$/ui', $code)) {
 289:                         $errmsg = $lineNum . '行目のデータが異常です';
 290:                         return FALSE;
 291:                     }
 292:                     $infoSpots[$code]['region'] = $region;
 293:                     $infoSpots[$code]['promotionBureau'] = $promotionBureau;
 294:                     $cnt++;
 295:                     break;
 296:                 // 観測所名
 297:                 case 4:
 298:                     $infoSpots[$code]['observatory'] = trim($str);
 299:                     $cnt++;
 300:                     break;
 301:                 // よみがな
 302:                 case 5:
 303:                     $cnt++;
 304:                     break;
 305:                 // ローマ字表記
 306:                 case 6:
 307:                     $cnt++;
 308:                     break;
 309:                 // 所在地
 310:                 case 7:
 311:                     $infoSpots[$code]['address'] = trim($str);
 312:                     $cnt++;
 313:                     break;
 314:             }
 315:         }
 316:     }
 317:     if (count($infoSpots[$code]) < 4) {
 318:         $errmsg = $lineNum . '行目のデータが異常です';
 319:         return FALSE;
 320:     }
 321:     return TRUE;
 322: }

CSV形式ファイルの1行分の処理は、下請けのユーザー関数が getWEBGTinfoSpot で行う。

envWBGTinfoInit.php

 324: /**
 325:  * 暑さ指数(WBGT)情報提供地点の配列からファイルへ出力する
 326:  * @param   object $pwt       pahooWeatherインスタンス
 327:  * @param   array  $spots     暑さ指数(WBGT)情報提供地点
 328:  * @param   string $xmlfile   出力ファイル名
 329:  * @param   string $errmsg    エラーメッセージ格納用
 330:  * @return  bool TRUE:ファイル出力成功/FALSE:失敗
 331: */
 332: function putXmlFile($pwt, $spots, $fname, &$errmsg) {
 333: 
 334:     // XMLのルートタグとXML宣言
 335:     $root = '<?xml version="1.0" encoding="' . INTERNAL_ENCODING . '" ?><envWBGTinfospots></envWBGTinfospots>';
 336:     // XML作成開始
 337:     $xml = new SimpleXMLElement($root);
 338: 
 339:     // 作成日
 340:     $date  = $xml->addChild('date', date('c'));
 341: 
 342:     // バージョン
 343:     $date  = $xml->addChild('version', $pwt::FILE_VERSION);
 344: 
 345:     // 地点情報
 346:     foreach($spots as $code=>$item) {
 347:         $spot = $xml->addChild('spot');
 348:         $spot->addChild('code', $code);
 349:         foreach ($item as $key=>$val) {
 350:             $spot->addChild($key, $val);
 351:         }
 352:     }
 353: 
 354:     if (($outfp = @fopen($fname, 'w')) !FALSE) {
 355:         // XMLを整形出力
 356:         $dom = new DOMDocument('1.0');
 357:         $dom->loadXML($xml->asXML() );
 358:         $dom->formatOutput = TRUE;
 359:         $res = @fwrite($outfp, $dom->saveXML()) == FALSE ? FALSE : TRUE;
 360:         fclose($outfp);
 361:         $dom = NULL;
 362:     } else {
 363:         $errmsg = $fname . ' の書き込みに失敗しました';
 364:         $res = FALSE;
 365:     }
 366: 
 367:     return $res;
 368: }

配列に格納されたデータは、ユーザー定義関数 putXmlFile によりXML形式ファイルに出力する。このとき、データの作成日とバージョンを追加する。

解説:暑さ指数情報提供地点一覧を配列へ読み込む

pahooWeather.php

 861: /**
 862:  * 環境省の暑さ指数(WBGT)情報提供地点一覧を配列へ読み込む
 863:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-57-01.shtm
 864:  * @param   なし
 865:  * @return  int 読み込んだ地点数
 866: */
 867: function readEnvWBGTinfoSpots() {
 868:     // 予報地点情報ファイルをプロパティに読み込む
 869:     $cnt = 0;
 870:     $xml = @simplexml_load_file(self::FILE_WBGTINFO_SPOTS);
 871:     // レスポンス・チェック
 872:     if (isset($xml->spot) == FALSE) {
 873:         $res = FALSE;
 874:             $this->error = TRUE;
 875:         $this->errmsg = '暑さ指数(WBGT)情報提供地点ファイル "' . self::FILE_JMASPOTS . '" がありません';
 876:     // バージョン・チェック
 877:     } else if ($xml->version !self::FILE_VERSION) {
 878:         $res = FALSE;
 879:         $this->error = TRUE;
 880:         $this->errmsg = '暑さ指数(WBGT)情報提供地点ファイル "' . self::FILE_JMASPOTS . '" のバージョンが違います';
 881:     } else {
 882:         // 必要な情報を配列へ格納
 883:         $cnt = 0;
 884:         foreach ($xml->spot as $spot) {
 885:             $code = (string)$spot->code;
 886:             $this->envWBGTinfoSpots[$code]['region']        = (string)$spot->region;
 887:             $this->envWBGTinfoSpots[$code]['promotionBureau']   = (string)$spot->promotionBureau;
 888:             $this->envWBGTinfoSpots[$code]['observatory']   = (string)$spot->observatory;
 889:             $this->envWBGTinfoSpots[$code]['address']       = (string)$spot->address;
 890:             $this->envWBGTinfoSpots[$code]['longitude']     = (float)$spot->longitude;
 891:             $this->envWBGTinfoSpots[$code]['latitude']      = (float)$spot->latitude;
 892:             $this->envWBGTinfoSpots[$code]['distance1']     = (int)$spot->distance1;
 893:             $this->envWBGTinfoSpots[$code]['distance2']     = (int)$spot->distance2;
 894:             $this->envWBGTinfoSpots[$code]['distance3']     = (int)$spot->distance3;
 895:             $cnt++;
 896:         }
 897:     }
 898:     return $cnt;
 899: }

暑さ指数情報を使ったプログラムを作るのに利用できる汎用処理は、メソッドとして pahooWeatherクラス の方に実装した。
ユーザー定義メソッド readEnvWBGTinfoSpots は、先ほど作成した暑さ指数情報提供地点一覧のXML形式ファイルを配列へ読み込むユーザー定義メソッドである。

解説:暑さ指数情報を配列へ読み込む

pahooWeather.php

 901: /**
 902:  * 環境省の暑さ指数(WBGT)予測値データを情報提供地点一覧に代入する
 903:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-57-01.shtm
 904:  * @param   array $items 暑さ指数(WBGT)予測値データを格納する配列
 905:  * @return  int 読み込んだ地点数
 906: */
 907: function readEnvWBGTforecast(&$items) {
 908:     // 暑さ指数(WBGT)情報提供地点一覧を配列へ読み込む
 909:     if ($this->readEnvWBGTinfoSpots() === FALSE) {
 910:         return FALSE;
 911:     }
 912: 
 913:     // 暑さ指数(WBGT)予測値データを読み込む;pahooCacheクラスを利用
 914:     $contents = $this->pcc->load('https://www.wbgt.env.go.jp/prev15WG/dl/yohou_all.csv');
 915: 
 916:     // 配列に展開する
 917:     $items = array_map(
 918:         fn($line) => str_getcsv($line, ',', '"', '\\'),
 919:         explode("\n", $contents)
 920:     );
 921: 
 922:     // 予測日時を取得する
 923:     $dt = array();
 924:     $n = 0;
 925:     foreach ($items[0as $val) {
 926:         if ($val !== '') {
 927:             $dt[$n] = $val;
 928:             $n++;
 929:         }
 930:     }
 931: 
 932:     // 暑さ指数(WBGT)予測値データを情報提供地点一覧に代入する
 933:     $cnt = 0;
 934:     for ($i = 1$i < count($items); $i++) {
 935:         $code = $items[$i][0];
 936:         if (isset($this->envWBGTinfoSpots[$code])) {
 937:             $this->envWBGTinfoSpots[$code]['dt'] = $items[$i][1];
 938:             for ($j = 2$j < $n$j++) {
 939:                 $this->envWBGTinfoSpots[$code][$dt[$j - 2]] = (int)round($items[$i][$j] / 10);
 940:             }
 941:             $cnt++;
 942:         }
 943:     }
 944: 
 945:     $items = $this->envWBGTinfoSpots;
 946: 
 947:     return $cnt;
 948: }

ユーザー定義メソッド readEnvWBGTforecast は、環境省の予測値データファイルを読み込んで、メソッド readEnvWBGTinfoSpots で作成した配列にデータを追加していく。

解説:暑さ指数に対応する色コードを返す

pahooWeather.php

 950: /**
 951:  * 環境省の暑さ指数(WBGT)に対応する色コード(16進コード)を返す
 952:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-57-01.shtm
 953:  * @param   int $w 暑さ指数
 954:  *                  21未満 青
 955:  *                  21~25 水色
 956:  *                  25~28 黄色
 957:  *                  28~31 オレンジ色
 958:  *                  31以上 赤
 959:  * @return  string 色コード
 960: */
 961: function getWBGTcolor($w) {
 962:     // 基準点(暑さ指数, R, G, B)
 963:     $stops = [
 964:         [ 0,   0,   0, 255],    // 青
 965:         [21,   0, 255, 255],    // 水色
 966:         [25, 255, 255,   0],    // 黄色
 967:         [28, 255, 165,   0],    // オレンジ
 968:         [31, 255,   0,   0]     // 赤
 969:     ];
 970: 
 971:     // 範囲外処理(最低値以下)
 972:     if ($w <$stops[0][0]) {
 973:         return sprintf("#%02X%02X%02X", $stops[0][1], $stops[0][2], $stops[0][3]);
 974:     }
 975:     // 範囲外処理(最高値以上)
 976:     if ($w >$stops[count($stops)-1][0]) {
 977:         $last = $stops[count($stops)-1];
 978:         return sprintf("#%02X%02X%02X", $last[1], $last[2], $last[3]);
 979:     }
 980: 
 981:     // 区間を探して補間
 982:     for ($i = 0$i < count($stops- 1$i++) {
 983:         [$w1, $r1, $g1, $b1] = $stops[$i];
 984:         [$w2, $r2, $g2, $b2] = $stops[$i+1];
 985: 
 986:         if ($w >$w1 && $w <$w2) {
 987:             $t = ($w - $w1) / ($w2 - $w1); // 区間内の割合(0〜1)
 988:             $r = (int)round($r1 + ($r2 - $r1* $t);
 989:             $g = (int)round($g1 + ($g2 - $g1* $t);
 990:             $b = (int)round($b1 + ($b2 - $b1* $t);
 991:             return sprintf("#%02X%02X%02X", $r, $g, $b);
 992:         }
 993:     }
 994: }

環境省の熱中症予防情報サイトでは、暑さ指数(WBGT)の値に応じて4種類の警報色を用意している。これに倣い、暑さ指数を引数に代入すると、警報色をRGBの16進コードで返すユーザー定義メソッドが getWBGTcolor である。暑さ指数(WBGT)の値に応じて中間色の形で返すようにした。後述する正六角形のエリアを塗りつぶすのに使う。

解説:初期値

envWBGTforecast.php

  65: // 初期値(START) =============================================================
  66: 
  67: // 地図サービス(WebAPI)の選択
  68: //    0:Google
  69: //    2:地理院地図・OSM
  70: define('MAPSERVICE', 2);
  71: 
  72: // 住所検索サービスの選択
  73: //    0:Google
  74: //    1:Yahoo!JAPAN
  75: //   11:HeartRails Geo API
  76: //   12:OSM Nominatim Search API
  77: define('GEOSERVICE', 1);
  78: 
  79: // 逆ジオコーディングサービスの選択
  80: //    0:Google
  81: //    1:Yahoo!JAPAN
  82: //   11:HeartRails Geo API
  83: //   21:簡易ジオコーディングサービス
  84: define('REVGEOSERVICE', 21);
  85: 
  86: // マップの表示サイズ(単位:ピクセル)
  87: define('MAP_WIDTH',  600);
  88: define('MAP_HEIGHT', 600);
  89: // マップID
  90: define('MAPID', 'map_id');
  91: // マップ中心マーカーのURL;表示しなくなければNULLにする
  92: define('CENTER_MARKER', NULL);
  93: 
  94: // 初期値
  95: define('DEF_LONGITUDE', 139.766667);    // 地図中心(経度)
  96: define('DEF_LATITUDE',  35.681111);     //    (緯度)
  97: define('DEF_QUERY',     '東京駅');      // 検索クエリ
  98: define('DEF_TYPE',      'roadmap');     // マップタイプ
  99: define('DEF_ZOOM',      9);             // ズーム
 100: define('DEF_CAT',       'address');     // カテゴリ(Yahoo!)
 101: define('OPACITY',       0.5);           // 警告色の透過度
 102: 
 103: // キャッシュ保持時間(分) 0:キャッシュしない ※本プログラムでは使用しない
 104: // 環境省へのアクセス負荷軽減のため,180分以上のキャッシュ保持をお勧めします.
 105: define('LIFE_CACHE', 180);
 106: 
 107: // キャッシュ・ディレクトリ ※本プログラムでは使用しない
 108: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
 109: // 保持するデータ量は約100Kバイトです.
 110: define('DIR_CACHE', './pcache/');
 111: 
 112: // 初期値(END) ===============================================================

次に、暑さ指数予測をするプログラム "envWBGTforecast.php" を見ていこう。
まず初期値だが、毎度のことだが、とくに「変更不可」の記載のないものは、コメントにあるとおりに自由に変更できる。

表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
住所検索サービスは、GoogleYahoo!JAPANHeartRails Geo APIOSM Nominatim Search API国土地理院ジオコーディングAPI から選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、GoogleYahoo!JAPANHeartRails Geo API簡易ジオコーディングサービスから選べる。あらかじめ、定数 REVGEOSERVICE に値を設定すること。

pahoCacheクラス で使うキャッシュ保持時間だが、予測データは3時間毎に更新される仕様なので、180分にした。これより短くてもいいのだが、環境省サイトに余計な不可をかけるようなるので、ご配慮を願いたい。
キャッシュ・ディレクトリは。約100Kバイトの予測データファイル(CSV形式ファイル)を1つ保持するだけなので、それだけの容量があれば十分である。

解説:正六角形描画スクリプト

envWBGTforecast.php

 439: /**
 440:  * 地図上に六角形を描くスクリプトを生成する
 441:  * @param   array  $forecasts 暑さ指数(WBGT)予測値データ
 442:  * @param   object $pgc       pahooGeoCodeインスタンス
 443:  * @param   object $pwt       pahooWeatherインスタンス
 444:  * @param   float  $lat, $lng マップ中心の緯度・経度
 445:  * @param   int    $api   0:Google Maps JavaScript(省略時)
 446:  *                       11:地理院地図・OSM(Leaflet使用)
 447:  * @return  string JavaScript
 448: */
 449: function jsWBGTforecast($forecasts, $pgc, $pwt, $latitude, $longitude, $api=0) {
 450:     // 日時キーを取得する
 451:     foreach ($forecasts as $forecast) {
 452:         $keys = array_keys($forecast);
 453:         sort($keys);
 454:         break;
 455:     }
 456: 
 457:     // 暑さ指数(WBGT)予測値データを描画用配列に展開する
 458:     $params = array();
 459:     $cnt = 0;
 460:     foreach ($forecasts as $code=>$forecast) {
 461:         foreach ($keys as $key) {
 462:             if (preg_match('/^[0-9]+$/ui', $key)) {
 463:                 $params[$cnt][0] = $key;
 464:                 $params[$cnt][1] = $forecast['latitude'];
 465:                 $params[$cnt][2] = $forecast['longitude'];
 466:                 $params[$cnt][3] = (int)(($forecast['distance1'+ $forecast['distance2']) / 3);
 467:                 $params[$cnt][4] = $pwt->getWBGTcolor($forecast[$key]);
 468:                 $params[$cnt][5] = OPACITY;
 469:                 $params[$cnt][6] = 0;
 470:                 $cnt++;
 471:             }
 472:         }
 473:     }
 474: 
 475:     // 警報領域を正六角形で塗り潰すスクリプトを作成する
 476:     $js = (MAPSERVICE == 0? jsHexagon_Gmap($params: jsHexagon_Leaflet($params);
 477: 
 478:     return $js;
 479: }

警報エリアを示す正六角形はJavaScriptで描く。
ユーザー定義関数 jsWBGTforecast は、読み込んだ予測値データの配列から、正六角形を描くのに必要な配列 $param へ変換し、代入する。
JavaScriptのスクリプトは、後述する関数を呼び出して生成する。

解説:正六角形描画スクリプト:Googleマップ用

envWBGTforecast.php

 192: /**
 193:  * 正六角形描画スクリプト:Googleマップ用
 194:  * 複数の正六角形を描く
 195:  * @param   arrat $params[]  描画パラメータ
 196:  *                  [0] 日時, [1] 緯度, [2] 経度, [3] 一辺の長さ(メートル),
 197:  *                  [4] 描画色(省略時=#FF0000), [5] 透明度(省略時=1)
 198:  *                  [6] 線の太さ(省略時=1)
 199:  * @return  string JavaScript
 200: */
 201: function jsHexagon_Gmap($params) {
 202:     // スクリプト
 203:     $js =<<< EOT
 204: // 描画した六角形オブジェクトを格納(あとで消去するときに使用)
 205: let hexagons = []; 
 206: 
 207: // 描画パラメータ
 208: const params = [
 209: 
 210: EOT;
 211: 
 212:     // パラメータをfor文に展開
 213:     foreach ($params as $param) {
 214:         if ($param[1] === 0)    continue;
 215:         if ($param[3> 50000)  $param[3] = 50000;
 216:         $color   = isset($param[4]) ? $param[4: '#FF0000';
 217:         $opacity = isset($param[5]) ? $param[5: 1;
 218:         $weight  = isset($param[6]) ? $param[6: 1;
 219:         $js .=<<< EOT
 220: [ {$param[0]}, {$param[1]}, {$param[2]}, {$param[3]}, '{$color}', {$opacity}, {$weight} ],
 221: 
 222: EOT;
 223:     }
 224: 
 225:     // 六角形を描く関数
 226:     $js .=<<< EOT
 227: ];
 228: 
 229: function __drawHexagon(map, lat0, lng0, r, color = '#FF0000', opacity = 0.5, weight = 1) {
 230:     const latMeter = 111000;
 231:     const lngMeter = latMeter * Math.cos(lat0 * Math.PI / 180);
 232:     const path = [];
 233: 
 234:     for (let i = 0; i < 6; i++) {
 235:         const angle = Math.PI / 3 * i;
 236:         const dx = (r * Math.cos(angle)) / lngMeter;
 237:         const dy = (r * Math.sin(angle)) / latMeter;
 238: 
 239:         const lat = lat0 + dy;
 240:         const lng = lng0 + dx;
 241: 
 242:         path.push({ lat, lng });
 243:     }
 244: 
 245:     const hexagon = new google.maps.Polygon({
 246:         paths: path,
 247:         strokeColor: color,
 248:         strokeOpacity: opacity,
 249:         strokeWeight: weight,
 250:         fillColor: color,
 251:         fillOpacity: opacity,
 252:     });
 253: 
 254:     hexagons.push(hexagon);
 255: 
 256:     hexagon.setMap(map);
 257:     return hexagon;
 258: }
 259: 
 260: MyMapFuncs.drawHexagon = function(key) {
 261:     hexagons.forEach(hexagon => hexagon.setMap(null));
 262:     hexagons = [];
 263:     for (let i = 0; i < params.length; i++) {
 264:         if (params[i][0] == key) {
 265:             __drawHexagon(map, params[i][1], params[i][2], params[i][3], params[i][4], params[i][5], params[i][6]);
 266:         }
 267:     }
 268: }
 269: 
 270: EOT;
 271: 
 272:     return $js;
 273: }

ユーザー定義関数 jsHexagon_Gmap は、GoogleMaps JavaScript で正六角形を描くための JavaScriptのスクリプトを生成する。

1つの正六角形を描くJavaScript関数が __drawHexagon である。正六角形の6辺の位置を緯度、経度で算出し、Polygonで接続して、内側を塗りつぶす。
最後に、1つの正六角形オブジェクト hexagonpush しておく。これは、スライダーを使って別の日時のマップを描画するときに、前に描いた正六角形をすべて消去してから新規に描く必要があるのだが、GoogleMaps JavaScriptでは描画した図形を一気に消去する命令がないので、六角形オブジェクトを自前で記憶しておき、それを消去するという形を取った。

予報地点の数だけ繰り返し __drawHexagon を呼び出して正六角形を描く JavaScript関数が drawHexagon である。冒頭で、前回描いた正六角形オブジェクト hexagon を消去する。
この関数は、GoogleMaps JavaScriptのコールバック関数 initMap の中に定義するのだが、スライダーを動かしたときにイベント駆動させる関数でもある。そこで、あらかじめ用意したグルーバルオブジェクト MyMapFuncs のプロパティとして drawHexagon を呼び出せるような形で定義している。

解説:正六角形描画スクリプト:Leafletマップ用

envWBGTforecast.php

 275: /**
 276:  * 正六角形描画スクリプト:Leafletマップ用
 277:  * 複数の正六角形を描く
 278:  * @param   arrat $params[]  描画パラメータ
 279:  *                  [0] 日時, [1] 緯度, [2] 経度, [3] 一辺の長さ(メートル),
 280:  *                  [4] 描画色(省略時=#FF0000), [5] 透明度(省略時=1)
 281:  *                  [6] 線の太さ(省略時=1)
 282:  * @return  string JavaScript
 283: */
 284: function jsHexagon_Leaflet($params) {
 285:     // スクリプト
 286:     $js =<<< EOT
 287: // 描画した六角形オブジェクトを格納(あとで消去するときに使用)
 288: let hexagons = []; 
 289: 
 290: // 描画パラメータ
 291: const params = [
 292: 
 293: EOT;
 294: 
 295:     // パラメータをfor文に展開
 296:     foreach ($params as $param) {
 297:         if ($param[1] === 0)    continue;
 298:         if ($param[3> 50000)  $param[3] = 50000;
 299:         $color   = isset($param[4]) ? $param[4: '#FF0000';
 300:         $opacity = isset($param[5]) ? $param[5: 1;
 301:         $weight  = isset($param[6]) ? $param[6: 1;
 302:         $js .=<<< EOT
 303: [ {$param[0]}, {$param[1]}, {$param[2]}, {$param[3]}, '{$color}', {$opacity}, {$weight} ],
 304: 
 305: EOT;
 306:     }
 307: 
 308:     // 六角形を描く関数
 309:     $js .=<<< EOT
 310: ];
 311: 
 312: function __drawHexagon(map, lat0, lng0, r, color = '#FF0000', opacity = 0.5, weight = 1) {
 313:     const latMeter = 111000;
 314:     const lngMeter = latMeter * Math.cos(lat0 * Math.PI / 180);
 315:     const path = [];
 316: 
 317:     for (let i = 0; i < 6; i++) {
 318:         const angle = Math.PI / 3 * i;
 319:         const dx = (r * Math.cos(angle)) / lngMeter;
 320:         const dy = (r * Math.sin(angle)) / latMeter;
 321: 
 322:         const lat = lat0 + dy;
 323:         const lng = lng0 + dx;
 324: 
 325:         path.push([lat, lng]);  // Leafletは [lat, lng] の配列で座標を指定
 326:     }
 327: 
 328:     // ポリゴン作成
 329:     const hexagon = L.polygon(path, {
 330:         color: color,
 331:         weight: weight,
 332:         fillColor: color,
 333:         fillOpacity: opacity,
 334:     }).addTo(map);
 335: 
 336:     hexagons.push(hexagon);
 337: 
 338:     return hexagon;
 339: }
 340: 
 341: MyMapFuncs.drawHexagon = function(key) {
 342:     // 描画済みの六角形をすべて消去
 343:     hexagons.forEach(hex => map.removeLayer(hex));
 344:     hexagons = [];
 345: 
 346:     for (let i = 0; i < params.length; i++) {
 347:         if (params[i][0] == key) {
 348:             __drawHexagon(
 349:                 map,
 350:                 params[i][1],
 351:                 params[i][2],
 352:                 params[i][3],
 353:                 params[i][4],
 354:                 params[i][5],
 355:                 params[i][6]
 356:             );
 357:         }
 358:     }
 359: };
 360: 
 361: EOT;
 362: 
 363:     return $js;
 364: }

ユーザー定義関数 jsHexagon_Leaflet は、前述の jsHexagon_Gmap をLeaflet用に移植したものである。プログラムの流れは同じなので説明は割愛する。

解説:予測日時を指定するスライダー

envWBGTforecast.php

 366: /**
 367:  * 予測日時を指定するスライダーを作成
 368:  * @param   array  $forecasts 暑さ指数(WBGT)予測値データ
 369:  * @param   object $pgc       pahooGeoCodeインスタンス
 370:  * @param   object $pwt       pahooWeatherインスタンス
 371:  * @param   float  $lat, $lng マップ中心の緯度・経度
 372:  * @param   int    $api   0:Google Maps JavaScript(省略時)
 373:  *                       11:地理院地図・OSM(Leaflet使用)
 374:  * @return  string JavaScript
 375: */
 376: function makeSlider($forecasts) {
 377:     // 日時キーを取得する
 378:     foreach ($forecasts as $forecast) {
 379:         $keys = array_keys($forecast);
 380:         sort($keys);
 381:         break;
 382:     }
 383: 
 384:     $width = MAP_WIDTH;
 385:     $html =<<< EOT
 386: <div style="width:{$width}px; text-align:center; margin-top:16px;" id="valueDisplay"></div>
 387: <input style="width:{$width}px;" type="range" name="slider" id="slider" min="0" max="0" step="1">
 388: <script>
 389: const keys = [
 390: 
 391: EOT;
 392: 
 393:     foreach ($keys as $key) {
 394:         if (preg_match('/^[0-9]+$/ui', $key)) {
 395:             $html .$key . ',';
 396:         }
 397:     }
 398: 
 399:     $html .=<<< EOT
 400: ];
 401: 
 402: // スライダーの設定
 403: const slider = document.getElementById('slider');
 404: slider.max = keys.length - 1;
 405: slider.value = 0;
 406: 
 407: // 値の表示
 408: const display = document.getElementById('valueDisplay');
 409: function updateDisplay() {
 410:     const index = parseInt(slider.value, 10);
 411:     const key   = `\${keys[index]}`;
 412:     const year  = key.slice(0, 4);
 413:     const month = key.slice(4, 6);
 414:     const day   = key.slice(6, 8);
 415:     const hour  = key.slice(8, 10);
 416:     display.innerHTML = `\${year}年\${month}月\${day}日 \${hour}時`;
 417: 
 418:     if (typeof MyMapFuncs.drawHexagon === 'function') {
 419:         MyMapFuncs.drawHexagon(`\${keys[index]}`);
 420:     }
 421: }
 422: slider.addEventListener('input', updateDisplay);
 423: 
 424: // 初期表示;1秒待ってから表示
 425: new Promise(function(resolve) {
 426:     setTimeout(function() {
 427:         resolve();
 428:     }, 1000);
 429: }).then(function() {
 430:     updateDisplay();
 431: });
 432: </script>
 433: 
 434: EOT;
 435: 
 436:     return $html;
 437: }

予測データは、全地点について、3時間毎3日分のデータが格納されている。そこで、スライダーを使って、時間経過による暑さ指数の変化をインタラクティブに表示できるようにする。そのためのユーザー関数 makeSlider である。
インタラクティブ表示は JavaScriptで実装した。スライダーの変化イベントをキャッチして、updateDisplay 関数を呼び出す。updateDisplay 関数は、前述の drawHexagon関数を呼び出し、六角形を描き直す。

ここで、drawHexagon関数は、前述の通り、GoogleMaps JavaScriptのコールバック関数 initMap の中に定義しているため、コールバックが呼ばれない限り存在しない。そこで、はじめて updateDisplay 関数を呼ぶときは、Promiseを使って1秒待ってから実行するようにした。Promiseを使った待ち時間処理については「6.5 ファイル・アクセス、同期・非同期、JSON」をご覧いただきたい。

参考サイト

(この項おわり)
header