目次
- サンプル・プログラム:実行例
- サンプル・プログラムのダウンロード
- 準備:PHP の https対応
- 準備:pahooGeoCode クラス
- 準備:pahooCache クラス
- 準備:暑さ指数(WBGT)予測値データファイル
- 準備:予測地点情報ファイル
- 解説:予測地点情報ファイルを作成する
- 解説:暑さ指数情報提供地点一覧を配列へ読み込む
- 解説:暑さ指数情報を配列へ読み込む
- 解説:暑さ指数に対応する色コードを返す
- 解説:初期値
- 解説:正六角形描画スクリプト
- 解説:正六角形描画スクリプト:Googleマップ用
- 解説:正六角形描画スクリプト:Leafletマップ用
- 解説:予測日時を指定するスライダー
- 参考サイト
サンプル・プログラム:実行例
サンプル・プログラムのダウンロード
| 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 が通ったディレクトリに配置すること。 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 1.1.0 | 2025/08/17 | .pahooEnv導入 |
| 1.0.0 | 2025/08/09 | 初版 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 1.1.0 | 2025/08/17 | .pahooEnv導入 |
| 1.0.0 | 2025/08/10 | 初版 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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週間毎に再作成する |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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の変更に対応 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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 | 初版 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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対応
Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dllLinuxでは --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 を利用するのであれば 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: }
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが pahooCacheクラス である。同梱のクラス・ファイル "pahooCache.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooCacheクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。
pahooCacheクラス の注意ポイントは、キャッシュ時間(単位:分)とキャッシュを保存するディレクトリをコンストラクタで指定している点だ。これらはプログラムによって変わるものである。インスタンス化するときの値は、pahooCacheクラス を利用するメイン・プログラムの方で解説する。
PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。
準備:pahooInputData 関数群
また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。
準備:暑さ指数(WBGT)予測値データファイル
このページの最下段に、「利用規約確認」ボタンがあるので、それをクリックすると、説明書(PDF形式)をダウンロードできる。CSV形式ファイルの種類、公開場所、データ形式は説明書に記載されている。
ここでは、「環境省の暑さ指数(WBGT)予測値等電子情報提供サービスについて」の「全地点の予測値データファイル」を利用する。
また、「当サイトのご利用にあたって」を熟読してから、データ利用をはじめてほしい。
準備:予測地点情報ファイル
説明書(PDF形式ファイル)の【別表1】に、地点番号と、観測所名、所在地(住所)の一覧表が載っている。この【別表1】をCSV形式ファイルなどで公開しているか探したのだが見つからなかった。次善の策として、PDF形式ファイルを Adobe Acrobatを使ってExcel形式に変換してからCSV形式ファイル形式ファイルに保存した。これが同梱の "envWBGTinfoSpots.csv" である。Adobe公式の PDFをCSVに変換する方法を使うと、無償でオンライン変換できることを確認している。
解説:予測地点情報ファイルを作成する
緯度、経度は地図にマッピングするために必要であるから、後述するプログラムによって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: }
冒頭に地方名がある行をデータ行として判定する仕組みになっているため。同じ地方名画記述されている【別表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: }
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: }
解説:暑さ指数情報提供地点一覧を配列へ読み込む
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: }
ユーザー定義メソッド 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[0] as $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: }
解説:暑さ指数に対応する色コードを返す
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: }
解説:初期値
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) ===============================================================
まず初期値だが、毎度のことだが、とくに「変更不可」の記載のないものは、コメントにあるとおりに自由に変更できる。
表示する地図は、Googleマップ、地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。
住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo API、OSM Nominatim Search API、国土地理院ジオコーディングAPI から選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
逆ジオコーディングサービスは、Google、Yahoo!JAPAN、HeartRails 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: }
ユーザー定義関数 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: }
1つの正六角形を描くJavaScript関数が __drawHexagon である。正六角形の6辺の位置を緯度、経度で算出し、Polygonで接続して、内側を塗りつぶす。
最後に、1つの正六角形オブジェクト hexagon を push しておく。これは、スライダーを使って別の日時のマップを描画するときに、前に描いた正六角形をすべて消去してから新規に描く必要があるのだが、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: }
解説:予測日時を指定するスライダー
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: }
インタラクティブ表示は JavaScriptで実装した。スライダーの変化イベントをキャッチして、updateDisplay 関数を呼び出す。updateDisplay 関数は、前述の drawHexagon関数を呼び出し、六角形を描き直す。
ここで、drawHexagon関数は、前述の通り、GoogleMaps JavaScriptのコールバック関数 initMap の中に定義しているため、コールバックが呼ばれない限り存在しない。そこで、はじめて updateDisplay 関数を呼ぶときは、Promiseを使って1秒待ってから実行するようにした。Promiseを使った待ち時間処理については「6.5 ファイル・アクセス、同期・非同期、JSON」をご覧いただきたい。
参考サイト
- 熱中症予防情報サイト:環境省
- 暑さ指数電子情報提供サービス:環境省
- 各種クラウド連携サービス(WebAPI)の登録方法:ぱふぅ家のホームページ
- PHPクラスの紹介:ぱふぅ家のホームページ

今回は、住所やランドマークをキーにして場所を検索し、その付近の暑さ指数予測を地図上に色づけ表示をするPHPプログラムを作ってみることにする。加えて、3時間毎3日分の予測データが得られることから、スライダーを使って任意の日時の暑さ指数予測をマッピングする。
(2025年8月17日).pahooEnv導入