PHPで天気予報を求める

(1/1)
現在、国内外を問わず様々な天気予報WebAPIがあるのだが、わが国独特の降水予報を出しているのは、気象庁をおいて他にない。
気象庁は、RSSやWebAPIを使って天気予報情報を提供していないのだが、気象庁防災情報XMLによって PULL型配信している。そこで今回は、気象庁防災情報XMLから各地の週間天気予報情報(天気、降水確率、最高・最低気温)を取得し、一覧表に表示するPHPプログラムを作ってみることにする。

(2024年4月22日)京都市,熱海市など3日間しか表示されなくなった地点があり,予報地点情報ファイルを更新した.
(2023年12月30日)予報地点情報ファイルを更新
(2023年8月27日)予報地点情報ファイルを更新

プログラムを実行する

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

PHPで天気予報を求める

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

圧縮ファイルの内容
jmaWeeklyWeather.phpサンプル・プログラム本体。
jmaweatherspots.xml予報地点情報ファイル。「PHPで天気予報を求める」参照。
jmaWeatherInit.php予報地点情報ファイル作成プログラム。「PHPで天気予報を求める」参照。
pahooWeather.php気象情報に関わるクラス pahooWeather。
気象情報に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
jmaWeeklyWeather.php 更新履歴
バージョン 更新日 内容
2.4.0 2023/02/11 pahooInputDataクラス導入
2.3 2021/07/26 ツイート機能を追加
2.2 2021/04/02 キャッシュ・システム導入:pahooCacheクラス
2.12 2021/03/15 取得時刻によって,情報無し項目がゼロになったり,表示日がズレる不具合を修正
2.11 2021/03/07 値が0のとき,PHP7以下で非表示となる不具合を修正
jmaWeatherInit.php 更新履歴
バージョン 更新日 内容
3.1.0 2023/03/18 地域気象観測所一覧のフォーマット変更に対応
3.0.0 2023/02/11 アメダスのページから自動ダウンロード対応
2.2 2022/03/12 気象庁防災情報XMLのhttps化に対応, キャッシュ・システム導入:pahooCacheクラス
2.11 2021/03/23 bug-fix,地域観測所一覧(アメダス)を最新に
2.1 2021/03/03 天気予報(2~3日予報)に対応
pahooWeather.php 更新履歴
バージョン 更新日 内容
5.4.1 2023/08/12 __construct() -- 不具合修正
5.4 2023/02/11 FILE_VERSION = 2.2 に更新
5.3 2022/03/10 気象庁防災情報XMLのhttps化に対応
5.2 2021/04/08 pahoo_simplexml_loadをpahooCache::simplexml_loadへ
5.1 2021/04/02 キャッシュ・システム導入:pahooCacheクラス
pahooCalendar.php 更新履歴
バージョン 更新日 内容
4.5.0 2024/03/17 ヒジュラ暦メソッドを追加
4.4.1 2024/03/17 getCabinetOfficeHolidayTable() -- bug-fix
4.4.0 2024/02/25 内閣府の祝日表を参照できるようにした
4.3.2 2023/02/11 getSolarTerm72() 表記改訂:水澤腹堅→水沢腹堅
4.3.1 2023/02/03 表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン,七十二候
pahooCache.php 更新履歴
バージョン 更新日 内容
1.1.1 2023/02/11 コメント追記
1.1 2021/04/08 simplexml_load()メソッド追加
1.0 2021/04/02 初版
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加

目次

気象庁防災情報XMLフォーマット

気象庁防災情報XMLフォーマットは、気象や地震、火山に関する情報を随時流している。
まず、下表の6つの Atomフィードにアクセスし、必要なXML情報のURLを求める。
フィードの構造(xml) feed title タイトル subtitle サブタイトル updated 配信日時 id ID rights 著作権表記 entry title XML情報タイトル(1) id XML情報のURL(1) updated 更新日時(1) author name 作者(1) content 内容説明(1) entry title XML情報タイトル(2) id XML情報のURL(2) updated 更新日時(2) author name 作者(2) content 内容説明(2)
XML情報のURLの命名規則は以下の通り。
https://www.data.jma.go.jp/developer/xml/data/yyyymmddhhmmss_番号_電文コード_連番.xml
府県週間天気予報情報は、長期フィードの定時の中にある電文コード VPFW50 に入っている。府県天気予報情報は、同じく長期フィードの定時の中にある電文コード VPFD51 に入っている。そこで、フィードから配信日時 yyyymmddhhmmss が最も大きく、VPFW50 または VPFD51 を含むURLを取り出せば、それが目指す情報XMLとなる。

VPFW50 は、当日を含む6~7日間の天気予報・降水確率・最低・最高気温などを提供しているが、予報地点が全国約70箇所と少ない。また、情報を取得する時間帯によっては、当日の予報情報が欠けていることがある。
一方、VPFD51 は、当日を含む2~3日間だけの情報だが、予報地点は全国約170箇所と多く、当日の予報情報も含まれている。
そこで、VPFW50VPFD51 の両方の情報を参照して7日間の天気予報情報を表示する「週間予報」と、VPFD51 だけ参照する「天気予報」の2種類の予報を選択できるようにした。メインプログラムの変数 $forecast の値によって切り替える。

VPFW50の構造

府県週間天気予報情報XML VPFW50 は、ある地域の1週間分の天気予報が収められており、構造は下記の通りである。ここから必要な情報を取り出す。
注意すべきは、1つのXMLファイルの中に1つまたは複数の予報地点が含まれていること。さらに、天気予報(アイコン画像を含む)と降水確率は「地方」に紐付いているが、最低気温・最高気温は「予報地点」に紐付いているということである。地方と予報地点は1対N対応のため、後述する予報値地点データファイルに用意しておくことにした。
また、TimeDefine タグに示される7日分の情報が格納されているのだが、XML情報の取得時間帯によって、1つ目(refID=1)の情報が、今日になるか明日になるか分かれる。そこで、TimeDefine を取得して、現在日時(PCの内蔵時計)と比較して決定することにした。
降水確率や気温は、XML情報の取得タイミングによっては、1つ目(refID=1)~3つ目(refID=3)が「値なし」になっていることがある。そこで、後述する情報XML VPFW50 から、今日~明後日(または明日~明後日)の情報を取り出し、補完することにする。
VPFW50の構造(xml) Report Control Title 府県週間天気予報 DateTime 配信日時 Status ステータス EditorialOffice 編集者 PublishingOffice 配信者 Head Title タイトル ReportDateTime 配信日時 TargetDateTime 予報対象日時 TargetDuration 予報間隔 InfoType 発表 InfoKind 情報の種類 InfoKindVersion 情報バージョン Body MeteorologicalInfos TimeSeriesInfo TimeDefines TimeDefine DateTime 予報日時(1) Duration 予報間隔(1) TimeDefine DateTime 予報日時(2) Duration 予報間隔(2) TimeDefine DateTime 予報日時(3) Duration 予報間隔(3) TimeDefine DateTime 予報日時(4) Duration 予報間隔(4) TimeDefine DateTime 予報日時(5) Duration 予報間隔(5) TimeDefine DateTime 予報日時(6) Duration 予報間隔(6) TimeDefine DateTime 予報日時(7) Duration 予報間隔(7) Item Kind Property Type 天気 WeatherPart Weather 天気予報(1) Weather 天気予報(2) Weather 天気予報(3) Weather 天気予報(4) Weather 天気予報(5) Weather 天気予報(6) Weather 天気予報(7) WeatherCodePart WeatherCode 天気予報用テロップ番号(1) WeatherCode 天気予報用テロップ番号(2) WeatherCode 天気予報用テロップ番号(3) WeatherCode 天気予報用テロップ番号(4) WeatherCode 天気予報用テロップ番号(5) WeatherCode 天気予報用テロップ番号(6) WeatherCode 天気予報用テロップ番号(7) Kind Property Type 降水確率 ProbabilityOfPrecipitationPart ProbabilityOfPrecipitation 降水確率(1) ProbabilityOfPrecipitation 降水確率(2) ProbabilityOfPrecipitation 降水確率(3) ProbabilityOfPrecipitation 降水確率(4) ProbabilityOfPrecipitation 降水確率(5) ProbabilityOfPrecipitation 降水確率(6) ProbabilityOfPrecipitation 降水確率(7) Kind Property Type 信頼度 ReliabilityClassPart ReliabilityClass 信頼度階級(1) ReliabilityClass 信頼度階級(2) ReliabilityClass 信頼度階級(3) ReliabilityClass 信頼度階級(4) ReliabilityClass 信頼度階級(5) ReliabilityClass 信頼度階級(6) ReliabilityClass 信頼度階級(7) Area Name 地方名 Code 地方コード MeteorologicalInfos TimeSeriesInfo TimeDefines TimeDefine DateTime 予報日時(1) Duration 予報間隔(1) TimeDefine DateTime 予報日時(2) Duration 予報間隔(2) TimeDefine DateTime 予報日時(3) Duration 予報間隔(3) TimeDefine DateTime 予報日時(4) Duration 予報間隔(4) TimeDefine DateTime 予報日時(5) Duration 予報間隔(5) TimeDefine DateTime 予報日時(6) Duration 予報間隔(6) TimeDefine DateTime 予報日時(7) Duration 予報間隔(7) Item Kind Property Type 最低気温 TemperaturePart Temperature 最低気温(1) Temperature 最低気温(2) Temperature 最低気温(3) Temperature 最低気温(4) Temperature 最低気温(5) Temperature 最低気温(6) Temperature 最低気温(7) Property Type 最低気温予測範囲 TemperaturePart Property Type 最低気温予測範囲 TemperaturePart Property Type 最高気温 TemperaturePart Temperature 最高気温(1) Temperature 最高気温(2) Temperature 最高気温(3) Temperature 最高気温(4) Temperature 最高気温(5) Temperature 最高気温(6) Temperature 最高気温(7) Property Type 最高気温予測範囲 TemperaturePart Property Type 最高気温予測範囲 TemperaturePart Station Name 予報地点名 Code 予報地点コード

VPFD51の構造

府県天気予報情報(R1)XML VPFD51 の構造は下記の通りである。ある地域の今日~明後日(または今日~明日)の天気予報が収められており、構造は下記の通りである。ここから必要な情報を取り出す。
注意すべきは、VPFW50 と同じで1つのXMLファイルの中に1つまたは複数の予報地点が含まれていること、VPFW50 の予報地方と異なる場合があること。
また、天気予報(アイコン画像を含む)は1日単位で3回分だが、降水確率は6時間毎の値で6回分、気温は12時間毎の値で4回分と、各々の予報間隔が異なっている。いちいち TimeDefine を取得して、本日(PCの内蔵時計)と比較していく必要がある。
降水確率や気温は、XML情報の取得タイミングによっては、1つ目(refID=1)~3つ目(refID=3)が「値なし」になっていることがある。
VPFW50の構造(xml) Report Control Title 府県週間天気予報 DateTime 配信日時 Status ステータス EditorialOffice 編集者 PublishingOffice 配信者 Head Title タイトル ReportDateTime 配信日時 TargetDateTime 予報対象日時 TargetDuration 予報間隔 InfoType 発表 InfoKind 情報の種類 InfoKindVersion 情報バージョン Body MeteorologicalInfos TimeSeriesInfo TimeDefines TimeDefine DateTime 予報日時(1) Duration 予報間隔(1) TimeDefine DateTime 予報日時(2) Duration 予報間隔(2) TimeDefine DateTime 予報日時(3) Duration 予報間隔(3) TimeDefine DateTime 予報日時(4) Duration 予報間隔(4) TimeDefine DateTime 予報日時(5) Duration 予報間隔(5) TimeDefine DateTime 予報日時(6) Duration 予報間隔(6) TimeDefine DateTime 予報日時(7) Duration 予報間隔(7) Item Kind Property Type 天気 WeatherPart Weather 天気予報(1) Weather 天気予報(2) Weather 天気予報(3) Weather 天気予報(4) Weather 天気予報(5) Weather 天気予報(6) Weather 天気予報(7) WeatherCodePart WeatherCode 天気予報用テロップ番号(1) WeatherCode 天気予報用テロップ番号(2) WeatherCode 天気予報用テロップ番号(3) WeatherCode 天気予報用テロップ番号(4) WeatherCode 天気予報用テロップ番号(5) WeatherCode 天気予報用テロップ番号(6) WeatherCode 天気予報用テロップ番号(7) Kind Property Type 降水確率 ProbabilityOfPrecipitationPart ProbabilityOfPrecipitation 降水確率(1) ProbabilityOfPrecipitation 降水確率(2) ProbabilityOfPrecipitation 降水確率(3) ProbabilityOfPrecipitation 降水確率(4) ProbabilityOfPrecipitation 降水確率(5) ProbabilityOfPrecipitation 降水確率(6) ProbabilityOfPrecipitation 降水確率(7) Kind Property Type 信頼度 ReliabilityClassPart ReliabilityClass 信頼度階級(1) ReliabilityClass 信頼度階級(2) ReliabilityClass 信頼度階級(3) ReliabilityClass 信頼度階級(4) ReliabilityClass 信頼度階級(5) ReliabilityClass 信頼度階級(6) ReliabilityClass 信頼度階級(7) Area Name 地方名 Code 地方コード MeteorologicalInfos TimeSeriesInfo TimeDefines TimeDefine DateTime 予報日時(1) Duration 予報間隔(1) TimeDefine DateTime 予報日時(2) Duration 予報間隔(2) TimeDefine DateTime 予報日時(3) Duration 予報間隔(3) TimeDefine DateTime 予報日時(4) Duration 予報間隔(4) TimeDefine DateTime 予報日時(5) Duration 予報間隔(5) TimeDefine DateTime 予報日時(6) Duration 予報間隔(6) TimeDefine DateTime 予報日時(7) Duration 予報間隔(7) Item Kind Property Type 最低気温 TemperaturePart Temperature 最低気温(1) Temperature 最低気温(2) Temperature 最低気温(3) Temperature 最低気温(4) Temperature 最低気温(5) Temperature 最低気温(6) Temperature 最低気温(7) Property Type 最低気温予測範囲 TemperaturePart Property Type 最低気温予測範囲 TemperaturePart Property Type 最高気温 TemperaturePart Temperature 最高気温(1) Temperature 最高気温(2) Temperature 最高気温(3) Temperature 最高気温(4) Temperature 最高気温(5) Temperature 最高気温(6) Temperature 最高気温(7) Property Type 最高気温予測範囲 TemperaturePart Property Type 最高気温予測範囲 TemperaturePart Station Name 予報地点名 Code 予報地点コード

準備:表示幅、表示列数など

  55: //表示幅(ピクセル)
  56: define('WIDTH', 700);
  57: 
  58: //地方セレクタ(北海道地方,東北地方‥‥)を使うかどうか
  59: // 0 = 使わない
  60: // 1 = 使う
  61: define('REGION', 1);
  62: 
  63: //天気予報の種類(初期値)
  64: // 0 = 天気予報
  65: // 1 = 週間予報
  66: define('DEF_FORECAST', 1);
  67: 
  68: //天気予報の表示列数
  69: define('COLUMNS', 7);
  70: 
  71: //予報の開始日:デフォルト値(0:今日,1:明日‥‥6)
  72: define('DEF_START', 0);
  73: 
  74: //予報の終了日:デフォルト値(0:今日,1:明日‥‥6)
  75: define('DEF_GOAL', 6);

一覧表の表示幅は WIDTH に設定している。
週間予報は最大7日分を取得できるが、横表示の日数を COLUMNS に設定している。
7なら1行で、ここを4にすると4日分と3日分の2行表示となる。
その他、「変更不可」以外の定数は自由に変更できる。

解説:キャッシュ・システム

1週間分の予報を表示するには、気象庁防災情報XMLから、
  1. Atomフィード(長期フィード:定時)
  2. VPFW50
  3. VPFD51
の3つのXMLファイルを読み込む必要がある。このうちAtomフィード(長期フィード:定時)は約4Mバイトと大きく、毎回、ロードすることは気象庁サイトへ負荷を掛けることになる。そこで、一定時間、自サイトにXMLファイルを保持しておきキャッシュ・システムを導入することにする。

  80: //キャッシュ保持時間(分) 0:キャッシュしない
  81: //気象庁へのアクセス負荷軽減のため,60分以上のキャッシュ保持をお勧めします.
  82: define('LIFE_CACHE', 120);
  83: 
  84: //キャッシュ・ディレクトリ
  85: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
  86: //最大150Mバイトを消費します.
  87: define('DIR_CACHE', './pcache/');

キャッシュ保持時間は定数 LIFE_CACHE に分で指定する。キャッシュ・ファイルを保存するディレクトリは、定数 DIR_CACHE で指定する。このディレクトリは、PHPプログラムからの書き込みを許可すること。

たとえば120分(=2時間)を指定した場合、気象庁防災情報XMLへのアクセスは
 1日24時間÷2=12回
だけ行えばいい。VPFW50やVPFD51のXMLファイルのサイズは約16Kバイト。VPFW50の対象地点が70箇所、VPFD51の対象地点が170箇所であることから、1日あたりのアクセス量の最大値は
 4.0M×12+16K×70×12+16×170×12=141Mバイト
となる。

  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 (! 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クラス に分離してある。

キャッシュ保持時間とキャッシュ・ファイルを保存するディレクトリはコンストラクタで指定する。ディレクトリが無い場合、コンストラクタ内で生成するようにしてある。

  95: /**
  96:  * 指定したキャッシュ・ファイルを削除する.
  97:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  98:  * @param   string $pat   削除するファイル名(正規表現指定可能)(省略可能)
  99:  * @param   int    $life  キャッシュ保持時間(分)(省略可能)
 100:  * @return  int 削除したファイル数
 101: */
 102: function delete($pat='.+', $life=self::LIFE_CACHE) {
 103:     $fullname = $this->dirCache . '*';
 104: 
 105:     $cnt = 0;
 106:     foreach (glob($fullnameas $name) {
 107:         //ファイルかつパターンにマッチしたファイルが削除対象
 108:         if (is_file($name&& (preg_match($pat, basename($name)) > 0)) {
 109:             $ft = filemtime($name);
 110:             //$lifeより古いファイルが削除対象
 111:             if ((time() - $ft> $life * 60) {
 112:                 if (unlink($name) == TRUE) {
 113:                     $cnt++;
 114:                 }
 115:             }
 116:         }
 117:     }
 118: 
 119:     return $cnt;
 120: }

 122: /**
 123:  * cURLを利用して指定したURLのコンテンツを取得する.
 124:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 125:  * @param   string $url URL
 126:  * @return  mixed コンテンツ/FALSE:読み込み失敗
 127: */
 128: function cLoad($url) {
 129:     $curl = curl_init($url);
 130:     curl_setopt($curl, CURLOPT_HEADER, FALSE);
 131:     curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
 132:     curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);  //サーバ証明書検証をスキップ
 133:     curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);  //  〃
 134:     $contents = curl_exec($curl);
 135:     if (($contents == FALSE|| (curl_errno($curl!0)) {
 136:         $this->error   = TRUE;
 137:         $this->errmsg  = '"' . $url . '" が取得できません';
 138:         return FALSE;
 139:     }
 140:     $arr = curl_getinfo($curl);
 141:     if ($arr['http_code'!200) {
 142:         $this->error   = TRUE;
 143:         $this->errmsg  = '"' . $url . '" が取得できません';
 144:         return FALSE;
 145:     }
 146:     curl_close($curl);
 147: 
 148:     return $contents;
 149: }

 151: /**
 152:  * 指定したURLからコンテンツを読み込む.
 153:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 154:  * @param   string $url   URL
 155:  * @return  mixed コンテンツ/FALSE:読み込み失敗
 156: */
 157: function forceLoad($url) {
 158:     $fname = $this->dirCache . md5($url);   //キャッシュするファイル名
 159: 
 160:     $res = $this->cLoad($url);
 161:     if ($res == FALSE) {
 162:         return FALSE;
 163:     }
 164:     $ret = file_put_contents($fname, $res);
 165:     if ($ret == FALSE) {
 166:         $this->error = TRUE;
 167:         $this->errmsg = 'キャッシュファイル "' . $fname . '" の書き込み失敗しました';
 168:         return FALSE;
 169:     }
 170:     $this->debug .$url . "\n";
 171: 
 172:     return $res;
 173: }

 175: /**
 176:  * 指定したキャッシュまたはURLからコンテンツを読み込む.
 177:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 178:  * @param   string $url URL
 179:  * @return  mixed コンテンツ/FALSE:読み込み失敗
 180: */
 181: function load($url) {
 182:     //キャッシュ有効
 183:     if ($this->lifeCache > 0) {
 184:         $this->delete('/[0-9a-f]+/ui', $this->lifeCache);   //古いキャッシュを削除
 185:         $fname = $this->dirCache . md5($url);   //キャッシュするファイル名
 186:         //キャッシュが存在する
 187:         if (is_file($fname)) {
 188:             $res = file_get_contents($fname);
 189:             if ($res == FALSE) {
 190:                 $this->error = TRUE;
 191:                 $this->errmsg = 'キャッシュファイル "' . $fname . '" の読み込みに失敗しました';
 192:                 return FALSE;
 193:             }
 194:             $this->debug .$fname . "\n";
 195:         //ネットから取得
 196:         } else {
 197:             $res = $this->forceLoad($url);
 198:         }
 199: 
 200:     //キャッシュ無効
 201:     } else {
 202:         $res = $this->forceLoad($url);
 203:     }
 204: 
 205:     return $res;
 206: }

コンテンツを読み込むメソッドは load である。
キャッシュが有効の時は、まず、delete メソッドを使って古いキャッシュを削除する。filemtime 関数を使ってファイルのタイムスタンプを取得し、それを現在日時と比較することで削除するかどうかを判定している。
ッシュへ書き込む。

キャッシュ・ファイル名は、URLからハッシュ関数  md5  を使って生成する。
キャッシュが存在すれば、 file_get_contents  を使って読み込む。
無ければ、forceLoad メソッドを使ってネットから読み込み、キャッシュへ書き込む。ネットからの読み込みは cLoad メソッドであるが、cURL関数を使って読み込んでいる。

 208: /**
 209:  * キャッシュ・システムを利用したSimple XMLロード.
 210:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 211:  * @param   string $url XMLファイル名(URL指定)
 212:  * @return  bool TRUE:取得成功/FALSE:失敗
 213: */
 214: function simplexml_load($url) {
 215:     //キャッシュ・システムを使ったロード
 216:     $contents = $this->load($url);
 217: 
 218:     //失敗したらネットから強制ロード
 219:     if ($this->iserror()) {
 220:         $this->error = FALSE;
 221:         $this->errmsg = '';
 222:         $contents = $this->forceLoad($url);
 223:         if ($this->iserror()) {
 224:             return FALSE;
 225:         }
 226:     }
 227: 
 228:     //XMLロード
 229:     $xml = @simplexml_load_string($contents);
 230: 
 231:     //キャッシュが壊れている可能性
 232:     if ($xml == FALSE) {
 233:         $contents = $this->forceLoad($url);
 234:         $xml = @simplexml_load_string($contents);
 235:         if ($xml == FALSE) {
 236:         $this->error   = TRUE;
 237:         $this->errmsg  = '"' . $url . '" が取得できません';
 238:             return FALSE;
 239:         }
 240:     }
 241: 
 242:     return $xml;
 243: }

 simplexml_load_file  関数を、このキャッシュ・システムに対応させたメソッドが simplexml_load である。

まず、前述の load メソッドを使ってキャッシュ・システムからコンテンツをロードする。
ここで失敗したら、キャッシュ・ファイルが破損している可能性があるので、forceLoad メソッドを使ってネットから取得する。

取得したコンテンツを  simplexml_load_string  に渡してXML解釈させるのだが、ここでエラーが発生した場合も、キャッシュ・ファイルが破損している可能性があるので、同様にネットからXMLファイルを取り直して、再度、 simplexml_load_string  へ流し込む。
PHPで天気予報を求める
デバッグモードにすると、キャッシュから読み込んでいるかどうか表示するようになっている。上図は、3つのXMLファイルともネットから読み込んでいる状態。
PHPで天気予報を求める
次が、VPFW50とVPFD51をキャッシュから読み込んでいる状態。
PHPで天気予報を求める
最後が、3つのファイルともキャッシュから読み込んでいる状態。

解説:予報地点情報ファイルを読み込む

 105: /**
 106:  * 予報地点情報ファイルをプロパティに読み込む.
 107:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 108:  * @param   なし
 109:  * @return  bool TRUE:読込成功/FALSE:失敗
 110: */
 111: function readJmaSpots() {
 112:     $cnt = 0;
 113: 
 114:     $xml = @simplexml_load_file(self::FILE_JMASPOTS);
 115:     //レスポンス・チェック
 116:     if (isset($xml->spot) == FALSE) {
 117:         $res = FALSE;
 118:             $this->error = TRUE;
 119:         $this->errmsg = '予報地点ファイル "' . self::FILE_JMASPOTS . '" がありません';
 120:     //バージョン・チェック
 121:     } else if ($xml->version !self::FILE_VERSION) {
 122:         $res = FALSE;
 123:             $this->error = TRUE;
 124:         $this->errmsg = '予報地点ファイル "' . self::FILE_JMASPOTS . '" のバージョンが違います';
 125:     } else {
 126:         //必要な情報を配列へ格納
 127:         foreach ($xml->spot as $spot) {
 128:             $this->spots[$cnt]['code']        = (string)$spot->code;
 129:             $this->spots[$cnt]['page']        = (string)$spot->page;
 130:             $this->spots[$cnt]['regionCode']  = (string)$spot->regionCode;
 131:             $this->spots[$cnt]['regionName']  = (string)$spot->regionName;
 132:             $this->spots[$cnt]['prefCode']    = (string)$spot->prefCode;
 133:             $this->spots[$cnt]['prefName']    = (string)$spot->prefName;
 134:             $this->spots[$cnt]['areaCode']    = (string)$spot->areaCode;
 135:             $this->spots[$cnt]['areaName']    = (string)$spot->areaName;
 136:             $this->spots[$cnt]['stationName'] = (string)$spot->stationName;
 137:             $this->spots[$cnt]['stationCode'] = (string)$spot->stationCode;
 138:             $this->spots[$cnt]['longitude']   = (float)$spot->longitude;
 139:             $this->spots[$cnt]['latitude']    = (float)$spot->latitude;
 140:             $this->spots[$cnt]['location']    = (float)$spot->location;
 141:             $cnt++;
 142:         }
 143:         if ($cnt == 0) {
 144:             $res = FALSE;
 145:             $this->error = TRUE;
 146:             $this->errmsg = '予報地点ファイル "' . self::FILE_JMASPOTS . '" がありません';
 147:         } else {
 148:             $res = TRUE;
 149:         }
 150:     }
 151:     return $res;
 152: }

天気予報情報を取得する処理は、クラス pahooWeather に分離している。
後述する予報地点情報ファイルを読み込むメソッド readJmaSpots はコンストラクタから呼ばれる。

解説:気象庁防災情報XMLから最新の週間天気予報情報URLを取得

 339: /**
 340:  * ページ番号を指定して気象庁防災情報XMLから最新の天気予報情報URLを求める.
 341:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 342:  * @param   int $page  ページ番号
 343:  * @return  array(VPFD51, VPFW50)/FALSE:取得失敗
 344: */
 345: function jmaGetWeatherForecastURL($page) {
 346:     //URLパターン
 347:     $vpfd51 = sprintf('/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VPFD51\_%06d\.xml/ui', $page);
 348:     $vpfw50 = sprintf('/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VPFW50\_%06d\.xml/ui', $page);
 349: 
 350:     $xml = $this->pcc->simplexml_load(self::FEED_REGULAR_L);
 351:     //レスポンス・チェック
 352:     if ($this->pcc->iserror()) {
 353:         $this->error  = TRUE;
 354:         $this->errmsg = $this->pcc->errmsg;
 355:         return FALSE;
 356:     } else if (! isset($xml->entry)) {
 357:         $this->error  = TRUE;
 358:         $this->errmsg = '"' . self::FEED_REGULAR_L . '" の解釈に失敗しました';
 359:         return FALSE;
 360:     }
 361:     $this->xmlfile[0] = self::FEED_REGULAR_L;
 362: 
 363:     //フィード(XMLファイル)解析
 364:     $vpfd51_url = $vpfd51_dt = '';
 365:     $vpfw50_url = $vpfw50_dt = '';
 366:     $res = FALSE;
 367:     foreach ($xml->entry as $node) {
 368:         //日時がより新しいURLを採用
 369:         if (preg_match($vpfd51, $node->id, $arr> 0) {
 370:             if ($arr[1> $vpfd51_dt) {
 371:                 $vpfd51_url = $arr[0];
 372:                 $vpfd51_dt  = $arr[1];
 373:                 $res = TRUE;
 374:             }
 375:         } else if (preg_match($vpfw50, $node->id, $arr> 0) {
 376:             if ($arr[1> $vpfw50_dt) {
 377:                 $vpfw50_url = $arr[0];
 378:                 $vpfw50_dt  = $arr[1];
 379:                 $res = TRUE;
 380:             }
 381:         }
 382:     }
 383: 
 384:     //エラー・チェック
 385:     if (! $res) {
 386:         $this->error  = TRUE;
 387:         $this->errmsg = '気象庁防災情報XMLから週間天気予報情報を取得できません';
 388:         return FALSE;
 389:     }
 390: 
 391:     $this->xmlfile[1] = $vpfw50_url;
 392:     $this->xmlfile[2] = $vpfd51_url;
 393: 
 394:     return array($vpfd51_url, $vpfw50_url);
 395: }

前述のフィードから、最新の府県週間天気予報情報XML VPFW50 と府県天気予報情報XML VPFD51 の情報URLを取得するユーザー関数が jmaGetWeatherForecastURL である。
URLを正規表現で分解し、配信日時 yyyymmddhhmmss が最も大きく、VPFW50 または VPFD51 を含むURLを返す。

解説:天気予報情報を読み込む

VPFW50VPFD51 から天気情報を配列 jma_WeeklyWeather へ格納していくメソッドが jmaGetWeatherForecast である。巨大なメソッドであるが、VPFW50 を取得する前半と、VPFD51 を取得する後半部分に分かれる。

 397: /**
 398:  * 予報地点コード$stationを指定し,天気予報情報を配列$itemsに格納する.
 399:  * 気象庁防災情報XMLを利用する.
 400:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
 401:  * @param   array  $items    天気予報を格納する配列
 402:  * @param   string $station  予報地点コード
 403:  * @param   int    $forecast 0:天気予報,1:週間天気予報(省略時:1)
 404:  *                  0のとき‥‥jmaWeeklyWeather[0](本日)~[6](6日後)に代入
 405:  *                  1のとき‥‥jmaWeeklyWeather[0](本日)~[2](2日後)に代入
 406:  * @return  bool TRUE:成功/FALSE:失敗
 407: */
 408: function jmaGetWeatherForecast(&$items, $station, $forecast=1) {
 409:     //電文コード
 410:     $code = ($forecast == 0? 'VPFD51' : 'VPFW50';
 411:     //名前空間
 412:     define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
 413:     //曜日
 414:     static $week_name = array('日', '月', '火', '水', '木', '金', '土');
 415:     //マッチングパターン
 416:     $pat11 = '/([0-9]+)\-([0-9]+)\-([0-9]+)/ui';                //年月日
 417:     $pat12 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:/ui';     //年月日時
 418:     $pat21 = '/から/ui';                                        //降水確率
 419:     $pat31 = '/最低気温/ui';                                    //最低気温
 420:     $pat32 = '/^日中の最高気温/ui';                             //最高気温
 421: 
 422:     //予報地点コードの取得
 423:     $res = FALSE;
 424:     foreach ($this->spots as $id=>$spot) {
 425:         if (($spot['code'] == $code&& ($spot['stationCode'] == $station)) {
 426:             $res = TRUE;
 427:             break;
 428:         }
 429:     }
 430:     if ($res == FALSE) {
 431:         $this->error  = TRUE;
 432:         $this->errmsg = '予報地点コードが見つかりません';
 433:         return FALSE;
 434:     }
 435: 
 436:     //初日のみ初期化
 437:     $i = 0;
 438:     $dt = new DateTime(date('Y-m-d'));
 439:     $this->jmaWeeklyWeather[$i]['year']  = date('Y');
 440:     $this->jmaWeeklyWeather[$i]['month'] = date('n');
 441:     $this->jmaWeeklyWeather[$i]['day']   = date('j');
 442:     $this->jmaWeeklyWeather[$i]['week']  = (string)$week_name[date_format($dt, 'w')];
 443:     $this->jmaWeeklyWeather[$i]['stationName'] = $this->spots[$id]['stationName'];
 444:     $this->jmaWeeklyWeather[$i]['weather']  = '';
 445:     $this->jmaWeeklyWeather[$i]['image']    = '';
 446:     $this->jmaWeeklyWeather[$i]['rainy']    = '';
 447:     $this->jmaWeeklyWeather[$i]['temp_max'] = '';
 448:     $this->jmaWeeklyWeather[$i]['temp_min'] = '';
 449: 
 450:     //最新の週間天気予報情報URLを取得
 451:     $page = $this->spots[$id]['page'];
 452:     $area = $this->spots[$id]['areaCode'];
 453:     $station = $this->spots[$id]['stationCode'];
 454: 
 455:     list($vpfd51, $vpfw50) = $this->jmaGetWeatherForecastURL($page);
 456:     if ($this->pcc->iserror()) {
 457:         $this->error  = TRUE;
 458:         $this->errmsg = $this->pcc->geterror();
 459:         return FALSE;
 460:     }
 461:     if ($this->error)       return FALSE;
 462:     if ($this->pcc->error)  return FALSE;
 463: 
 464:     $this->jmaWeeklyWeather['stationCode']  = $this->spots[$id]['stationCode'];
 465:     $this->jmaWeeklyWeather['stationName']  = $this->spots[$id]['stationName'];
 466:     $this->jmaWeeklyWeather['location']     = $this->spots[$id]['location'];
 467: 
 468:     //VPFW51(府県週間天気予報)の解析
 469:     if ($forecast == 1) {
 470:         $xml = $this->pcc->simplexml_load($vpfw50);
 471:         //レスポンス・チェック
 472:         if ($this->pcc->iserror()) {
 473:             $this->error  = TRUE;
 474:             $this->errmsg = $this->pcc->errmsg;
 475:             return FALSE;
 476:         } else if (! isset($xml->Body->MeteorologicalInfos)) {
 477:             $this->error  = TRUE;
 478:             $this->errmsg = '"' . $vpfw50 . '" の解釈に失敗しました';
 479:             return FALSE;
 480:         }
 481: 
 482:         //年月日取得
 483:         foreach ($xml->Body->MeteorologicalInfos->TimeSeriesInfo->TimeDefines->TimeDefine as $TimeDefine) {
 484:             if (preg_match($pat11, $TimeDefine->DateTime, $arr> 0) {
 485:                 //最初の情報は何時か
 486:                 $i = (int)$TimeDefine['timeId'];
 487:                 if ($i == 1) {
 488:                     //$dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
 489:                     //v.5.02
 490:                     $ss2 = date('Y-m-d');
 491:                     if ($ss2 < $arr[0])          $dd = 0;        //明日
 492:                     else if ($ss2 == $arr[0])   $dd = (-1);     //今日
 493:                     else                        $dd = (-2);     //昨日
 494:                 }
 495:                 $i +$dd;
 496:                 if ($i < 0)      continue;   //v.5.02
 497:                 //予報1日分の初期化
 498:                 $dt = new DateTime($arr[0]);
 499:                 $this->jmaWeeklyWeather[$i]['year']  = (int)$arr[1];
 500:                 $this->jmaWeeklyWeather[$i]['month'] = (int)$arr[2];
 501:                 $this->jmaWeeklyWeather[$i]['day']   = (int)$arr[3];
 502:                 $this->jmaWeeklyWeather[$i]['week']  = (string)$week_name[date_format($dt, 'w')];
 503:                 $this->jmaWeeklyWeather[$i]['weather']  = '';
 504:                 $this->jmaWeeklyWeather[$i]['image']    = '';
 505:                 $this->jmaWeeklyWeather[$i]['rainy']    = '';
 506:                 $this->jmaWeeklyWeather[$i]['temp_max'] = '';
 507:                 $this->jmaWeeklyWeather[$i]['temp_min'] = '';
 508:             }
 509:         }
 510: 
 511:         foreach ($xml->Body->MeteorologicalInfos as $info) {
 512:             //天気・降水確率の取得
 513:             if ((string)$info['type'] == '区域予報') {
 514:                 foreach ($info->TimeSeriesInfo->Item as $item) {
 515:                     if ((string)$item->Area->Code !$area)     continue;
 516:                     foreach ($item->Kind as $kind) {
 517:                         if ((string)$kind->Property->Type == '天気') {
 518:                             $node = $kind->Property->WeatherPart->children(JMX_EB);
 519:                             foreach ($node->Weather as $Weather) {
 520:                                 $id = (int)$Weather->attributes()->refID + $dd;
 521:                                 if ($id < 0continue;   //v.5.02
 522:                                 $this->jmaWeeklyWeather[$id]['weather'] = $this->jmaShortWeather((string)$Weather);
 523:                             }
 524:                             $node = $kind->Property->WeatherCodePart->children(JMX_EB);
 525:                             foreach ($node->WeatherCode as $WeatherCode) {
 526:                                 $id = (int)$WeatherCode->attributes()->refID + $dd;
 527:                                 if ($id < 0continue;   //v.5.02
 528:                                 //当日夜間
 529:                                 if (($id == 0&& (date('H'>18)) {
 530:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jmaTelop2url((int)$WeatherCode, 1);
 531:                                 } else {
 532:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jmaTelop2url((int)$WeatherCode, 0);
 533:                                 }
 534:                             }
 535:                         } else if ((string)$kind->Property->Type == '降水確率') {
 536:                             $node = $kind->Property->ProbabilityOfPrecipitationPart->children(JMX_EB);
 537:                             foreach ($node->ProbabilityOfPrecipitation as $rainy) {
 538:                                 $id = (int)$rainy->attributes()->refID + $dd;
 539:                                 if ($id < 0continue;   //v.5.02
 540:                                 $this->jmaWeeklyWeather[$id]['rainy'] = (string)$rainy;
 541:                             }
 542:                         }
 543:                     }
 544:                 }
 545:             //最低気温・最高気温
 546:             } else if ((string)$info['type'] == '地点予報') {
 547:                 foreach ($info->TimeSeriesInfo->Item as $item) {
 548:                     if ((int)$item->Station->Code !$station)  continue;
 549:                     foreach ($item->Kind->Property as $property) {
 550:                         if ((string)$property->Type == '最低気温') {
 551:                             $node = $property->TemperaturePart->children(JMX_EB);
 552:                             foreach ($node->Temperature as $Temperature) {
 553:                                 $id = (int)$Temperature->attributes()->refID + $dd;
 554:                                 if ($id < 0continue;   //v.5.02
 555:                                 $this->jmaWeeklyWeather[$id]['temp_min'] = (string)$Temperature;    //v.5.02
 556:                             }
 557:                         } else if ((string)$property->Type == '最高気温') {
 558:                             $node = $property->TemperaturePart->children(JMX_EB);
 559:                             foreach ($node->Temperature as $Temperature) {
 560:                                 $id = (int)$Temperature->attributes()->refID + $dd;
 561:                                 if ($id < 0continue;   //v.5.02
 562:                                 $this->jmaWeeklyWeather[$id]['temp_max'] = (string)$Temperature;    //v.5.02
 563:                             }
 564:                         }
 565:                     }
 566:                 }
 567:             }
 568:         }
 569:     }
 570: 
 571:     //VPFD51(府県天気予報 R1)の解析
 572:     //v.5.03修正(ここから)
 573:     $code = 'VPFD51';
 574:     //予報地点コードの取得
 575:     $res = FALSE;
 576:     foreach ($this->spots as $id=>$spot) {
 577:         if (($spot['code'] == $code&& ($spot['stationCode'] == $station)) {
 578:             $res = TRUE;
 579:             break;
 580:         }
 581:     }
 582:     if ($res == FALSE) {
 583:         $this->error  = TRUE;
 584:         $this->errmsg = '予報地点コードが見つかりません';
 585:         return FALSE;
 586:     }
 587:     $area = $this->spots[$id]['areaCode'];
 588:     //v.5.03修正(ここまで)
 589: 
 590:     $xml = $this->pcc->simplexml_load($vpfd51);
 591:     //レスポンス・チェック
 592:     if ($this->pcc->iserror()) {
 593:         $this->error  = TRUE;
 594:         $this->errmsg = $this->pcc->errmsg;
 595:         return FALSE;
 596:     } else if (! isset($xml->Body->MeteorologicalInfos)) {
 597:         $this->error  = TRUE;
 598:         $this->errmsg = '"' . $vpfd51 . '" の解釈に失敗しました';
 599:         return FALSE;
 600:     }
 601: 
 602:     //初期化
 603:     $rain_table = array('-', '-', '-', '-', '-', '-', '-', '-');
 604:     $temp_table = array(0, 0, 0, 0);
 605: 
 606:     foreach ($xml->Body->MeteorologicalInfos as $info) {
 607:         if ((string)$info['type'] == '区域予報') {
 608:             foreach ($info->TimeSeriesInfo as $TimeSeriesInfo) {
 609:                 //日時
 610:                 foreach ($TimeSeriesInfo->TimeDefines->TimeDefine as $TimeDefine) {
 611:                     if (preg_match($pat21, $TimeDefine->Name, $arr> 0) {
 612:                         preg_match($pat12, $TimeDefine->DateTime, $arr);
 613:                         $ss1 = $arr[1. '-' . $arr[2. '-' . $arr[3];
 614:                         //最初の情報は何時か
 615:                         $i = (int)$TimeDefine['timeId'];
 616:                         if ($i == 1) {
 617:                         //  $dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
 618:                             //v.5.02
 619:                             $ss2 = date('Y-m-d');
 620:                             if ($ss2 < $ss1)         $dd = 0;        //明日
 621:                             else if ($ss2 == $ss1)      $dd = (-1);     //今日
 622:                             else                        $dd = (-2);     //昨日
 623:                             $d2 = $arr[4] / 6;      //6時間毎
 624:                             //v.5.02
 625:                             if ($dd == (-2))    $d2 = $d2 - 4 + 1;
 626:                             //年月日代入(天気予報の場合)
 627:                             if ($forecast == 0) {
 628:                                 $dt = new DateTime($arr[1. '-' . $arr[2. '-' . $arr[3]);
 629:                                 for ($j = 0$j < 3$j++) {
 630:                                     $this->jmaWeeklyWeather[$j]['year']  = (int)date_format($dt, 'Y');
 631:                                     $this->jmaWeeklyWeather[$j]['month'] = (int)date_format($dt, 'n');
 632:                                     $this->jmaWeeklyWeather[$j]['day']   = (int)date_format($dt, 'j');
 633:                                     $this->jmaWeeklyWeather[$j]['week']  = (string)$week_name[date_format($dt, 'w')];
 634:                                     date_add($dt, date_interval_create_from_date_string('1 days'));
 635:                                 }
 636:                             }
 637:                         }
 638: 
 639:                     } else if (preg_match($pat11, $TimeDefine->DateTime, $arr> 0) {
 640:                         //最初の情報は今日か明日か
 641:                         $i = (int)$TimeDefine['timeId'];
 642:                         if ($i == 1) {
 643:                         //  $dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
 644:                             //v.5.02
 645:                             $ss2 = date('Y-m-d');
 646:                             if ($ss2 < $arr[0])          $dd = 0;        //明日
 647:                             else if ($ss2 == $arr[0])   $dd = (-1);     //今日
 648:                             else                        $dd = (-2);     //昨日
 649: 
 650:                         }
 651:                     }
 652:                 }
 653:                 //地方コードは上何桁で判定するか
 654:                 $flag = FALSE;
 655:                 $n = 5;
 656:                 for ($n = 5$n >1$n--) {
 657:                     foreach ($TimeSeriesInfo->Item as $item) {
 658:                         if (substr((string)$item->Area->Code, 0, $n!substr($area, 0, $n))       continue;
 659:                         $flag = TRUE;
 660:                     }
 661:                     if ($flag)  break;
 662:                 }
 663:                 //天気予報
 664:                 foreach ($TimeSeriesInfo->Item as $item) {
 665:                     foreach ($item->Kind as $kind) {
 666:                         if (substr((string)$item->Area->Code, 0, $n!substr($area, 0, $n))       continue;
 667:                         if ((string)$kind->Property->Type == '天気') {
 668:                             $node = $kind->Property->WeatherPart->children(JMX_EB);
 669:                             foreach ($node->Weather as $Weather) {
 670:                                 $id = (int)$Weather->attributes()->refID + $dd;
 671:                                 if ($id < 0continue;   //v.5.02
 672:                                 $this->jmaWeeklyWeather[$id]['weather'] = $this->jmaShortWeather((string)$Weather);
 673:                             }
 674:                             $node = $kind->Property->WeatherCodePart->children(JMX_EB);
 675:                             foreach ($node->WeatherCode as $WeatherCode) {
 676:                                 $id = (int)$WeatherCode->attributes()->refID + $dd;
 677:                                 if ($id < 0continue;   //v.5.02
 678:                                 if (($id == 0&& (date('H'>18)) {
 679:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jmaTelop2url((int)$WeatherCode, 1);
 680:                                 } else {
 681:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jmaTelop2url((int)$WeatherCode, 0);
 682:                                 }
 683:                             }
 684:                         //降水確率
 685:                         } else if ((string)$kind->Property->Type == '降水確率') {
 686:                             $node = $kind->Property->ProbabilityOfPrecipitationPart->children(JMX_EB);
 687:                             foreach ($node->ProbabilityOfPrecipitation as $rainy) {
 688:                                 $id = (int)$rainy->attributes()->refID + $dd + $d2;
 689:                                 if ($id < 0continue;   //v.5.02
 690:                                 $rain_table[$id] = (string)$rainy;
 691:                             }
 692:                         }
 693:                     }
 694:                 }
 695:             }
 696:         } else if ((string)$info['type'] == '地点予報') {
 697:             foreach ($info->TimeSeriesInfo as $TimeSeriesInfo) {
 698:                 //日時
 699:                 foreach ($TimeSeriesInfo->TimeDefines->TimeDefine as $TimeDefine) {
 700:                     if (preg_match($pat11, $TimeDefine->DateTime, $arr> 0) {
 701:                         //最初の情報は何時か
 702:                         $i = (int)$TimeDefine['timeId'];
 703:                         if ($i == 1) {
 704:                         //  $dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
 705:                             //v.5.02
 706:                             $ss2 = date('Y-m-d');
 707:                             if ($ss2 < $arr[0])          $dd = 0;        //明日
 708:                             else if ($ss2 == $arr[0])   $dd = (-1);     //今日
 709:                             else                        $dd = (-2);     //昨日
 710:                             $temp_table[$i] = 0;
 711:                             $day0 = (int)$arr[3];
 712:                         } else {
 713:                             $temp_table[$i] = ($day0 == (int)$arr[3]) ? 0 : 1;
 714:                             $day0 = (int)$arr[3];
 715:                         }
 716:                     }
 717:                 }
 718:                 //最低気温・最高気温
 719:                 $id = 1 + $dd;
 720:                 foreach ($TimeSeriesInfo->Item as $item) {
 721:                     if ((int)$item->Station->Code !$station)  continue;
 722:                     foreach ($item->Kind->Property as $property) {
 723:                         if (preg_match($pat31, (string)$property->Type> 0) {
 724:                             $node = $property->TemperaturePart->children(JMX_EB);
 725:                             $id +$temp_table[(int)$node->Temperature->attributes()->refID];
 726:                             if ($id < 0continue;   //v.5.02
 727:                             $this->jmaWeeklyWeather[$id]['temp_min'] = (string)$node->Temperature;      //v.5.02
 728:                         } else if (preg_match($pat32, (string)$property->Type> 0) {
 729:                             $node = $property->TemperaturePart->children(JMX_EB);
 730:                             $id +$temp_table[(int)$node->Temperature->attributes()->refID];
 731:                             if ($id < 0continue;   //v.5.02
 732:                             $this->jmaWeeklyWeather[$id]['temp_max'] = (string)$node->Temperature;      //v.5.02
 733:                         }
 734:                     }
 735:                 }
 736:             }
 737:         }
 738:     }
 739:     //降水確率を天気予報情報へ
 740:     foreach ($rain_table as $key=>$val) {
 741:         //v.5.02
 742:         if (($dd == (-2)) && ($key >4))   break;
 743:         $id = floor($key / 4);
 744:         if (! isset($this->jmaWeeklyWeather[$id]['rainy'])) {
 745:             $this->jmaWeeklyWeather[$id]['rainy'] = '';
 746:         }
 747:         //週間予報で取得した降水確率があれば上書き
 748:         if (isset($this->jmaWeeklyWeather[$id]['rainy']) && is_numeric($this->jmaWeeklyWeather[$id]['rainy'])) {
 749:             $this->jmaWeeklyWeather[$id]['rainy'] = '';
 750:         }
 751:         $this->jmaWeeklyWeather[$id]['rainy'.= (string)$val;
 752:         if ($key % 4 !3)  $this->jmaWeeklyWeather[$id]['rainy'.'/';
 753:     }
 754:     //不明要素を空文字で埋める(VPFD51の場合)
 755:     if ($forecast == 0) {
 756:         for ($i = 0$i < 3$i++) {
 757:             if (! isset($this->jmaWeeklyWeather[$i]['weather'])) {
 758:                 $this->jmaWeeklyWeather[$i]['weather'] = '';
 759:             }
 760:             if (! isset($this->jmaWeeklyWeather[$i]['image'])) {
 761:                 $this->jmaWeeklyWeather[$i]['image'] = '';
 762:             }
 763:             if (! isset($this->jmaWeeklyWeather[$i]['rainy'])) {
 764:                 $this->jmaWeeklyWeather[$i]['rainy'] = '';
 765:             }
 766:             if (! isset($this->jmaWeeklyWeather[$i]['temp_min'])) {
 767:                 $this->jmaWeeklyWeather[$i]['temp_min'] = '';
 768:             }
 769:             if (! isset($this->jmaWeeklyWeather[$i]['temp_max'])) {
 770:                 $this->jmaWeeklyWeather[$i]['temp_max'] = '';
 771:             }
 772:         }
 773:     }
 774: 
 775:     //配列へ代入
 776:     $items = $this->jmaWeeklyWeather;
 777:     return TRUE;
 778: }

VPFW50 の解析では、冒頭、情報XMLにある年月日と本日年月日を比較し、配列 jma_WeeklyWeather の格納位置を決める。jma_WeeklyWeather&x#5B;0&x#5D; は今日の情報を格納する。
曜日(月,火,水‥‥)は情報XMLにないので、 date_format  関数によって曜日番号を計算し、あらかじめ用意した曜日名テーブル $week_name を引き当てる。

要素 Weather にある天気予報(日本語)は文字列として長いので、ユーザー・メソッド jmaShortWeather を使って平仮名等を省略することで短縮する。
要素 WeatherCode にある天気予報テロップ番号は、以前は天気予報アイコン画像ファイルと1対1対応だったのだが、気象庁サイト・リニューアルにともない、アイコンがSVGファイルとなり、対応もN対1となった。そこで、気象庁週間予報ホームページを解析し、天気予報テロップ番号から対応するSVGを引き当てるユーザー・メソッド jma_telop2url を用意した。

VPFD51 の解析では、VPFD51の構造で述べたとおり、天気予報(アイコン画像を含む)は1日単位で3回分だが、降水確率は6時間毎の値で6回分、気温は12時間毎の値で4回分と、各々の予報間隔が異なっている。いちいち要素 TimeDefine の値を取得して、本日と比較するよう工夫している。
天気予報(VPFD51 のみ取得)の場合は、このループ処理で年月日も代入しておく。

VPFD51 では、地方コードが VPFW50 と異なる場合がある。そこで、VPFW50VPFW50 の各々の地方コード(5桁)を、上位5桁、4桁、3桁‥‥と順々に減らし、一致したコードを同じ地方と判断するようにした。

前述の通り、降水確率は6時間毎で開始位置が他の要素とは異なるため、いったん、配列 $rain_table に格納し、最後に配列 $jma_WeeklyWeather に代入するよう工夫した。
さらに、情報取得の時間帯によっては予報情報が入っていない場合があるので、最後に不明要素を空文字で埋めるようにした。

解説:週間天気予報表を作成する

 527: /**
 528:  * 指定した週間天気予報情報などから週間天気予報表(HTML)を作成する.
 529:  * @param   array $items    週間天気予報情報を格納した配列
 530:  * @param   int   $start    作成開始オフセット(0~6)
 531:  * @param   int   $goal     作成終了オフセット(0~6)
 532:  * @param   int   $columns  天気予報の表示列数(初期値:COLUMNS)
 533:  * @param   int   $forecast 0:天気予報,1:週間天気予報(省略時:1)
 534:  * @return  int 配列に格納した情報件数/0=失敗
 535: */
 536: function makeWeeklyWeather($items, $start, $goal, $columns=COLUMNS, $forecast=1) {
 537:     //引数のベリファイ
 538:     if ($start   < 0 || $start   > 6)    return FALSE;
 539:     if ($goal    < 0 || $goal    > 6)    return FALSE;
 540:     if ($columns < 0 || $columns > 7)    return FALSE;
 541:     if ($start > $goal)                 return FALSE;
 542:     if ($forecast == 0) {
 543:         if ($start   > 2)   return FALSE;
 544:         if ($goal    > 2)   return FALSE;
 545:         if ($columns > 3)   return FALSE;
 546:     }
 547: 
 548:     $outstr =<<< EOT
 549: <table class="weather">
 550: <caption>{$items['stationName']}の天気予報</caption>
 551: 
 552: EOT;
 553: 
 554:     $i = $start;
 555:     while ($i <$goal) {
 556:         //日付の行
 557:         $j = 0;
 558:         while ($j < $columns) {
 559:             if ($j == 0)    $outstr ."<tr>\n";
 560:             if (($i <$goal&& isset($items[$i]['week'])) {
 561:                 $mmddww = sprintf("%02d/%02d<span class=\"wsmall\">(%s)</span>", $items[$i]['month'], $items[$i]['day'], $items[$i]['week']);
 562:                 $outstr .=<<< EOT
 563: <td class="dt">{$mmddww}</td>
 564: 
 565: EOT;
 566:             } else {
 567:                 $outstr ."<td class=\"dt\">&nbsp;</td>\n";
 568:             }
 569:             $i++;
 570:             $j++;
 571:             if ($j == $columns$outstr ."</tr>\n";
 572:         }
 573:         //天気予報の行
 574:         $i -$columns;
 575:         $j = 0;
 576:         while ($j < $columns) {
 577:             //新しい行
 578:             if ($j == 0)    $outstr ."<tr>\n";
 579:             if ($i <$goal) {
 580:                 $mmddww = sprintf("%02d/%02d<span class=\"wsmall\">(%s)</span>", $items[$i]['month'], $items[$i]['day'], $items[$i]['week']);
 581:                 $temp_max = ((string)$items[$i]['temp_max'!''? $items[$i]['temp_max'. '℃' : '-';
 582:                 $temp_min = ((string)$items[$i]['temp_min'!''? $items[$i]['temp_min'. '℃' : '-';
 583:                 $outstr .=<<< EOT
 584: <td class="info">
 585: {$items[$i]['weather']}<br />
 586: <img class="wicon" src="{$items[$i]['image']}" /><br />
 587: <span class="wsmall">
 588: {$items[$i]['rainy']}%<br />
 589: {$temp_max}/{$temp_min}
 590: </span>
 591: </td>
 592: 
 593: EOT;
 594:             //情報なし列
 595:             } else {
 596:                 $outstr ."<td class=\"info\">&nbsp;</td>\n";
 597:             }
 598:             $i++;
 599:             $j++;
 600:             if ($j == $columns$outstr ."</tr>\n";
 601:         }
 602:     }
 603:     $outstr ."</table>\n";
 604: 
 605:     return $outstr;
 606: }

情報が取得できたら、それを出力用の表形式に加工するのがユーザー関数 makeWeeklyWeather である。

解説:表示とURLパラメータ

 718: //パラメータを取得する.
 719: $id       = (int)getParam('id',  FALSE, 0);
 720: $region   = (string)sprintf('%02d', (int)getParam('region',  FALSE, 0));
 721: $pref     = (string)sprintf('%02d', (int)getParam('pref',    FALSE, 0));
 722: $station  = (string)sprintf('%05d', (int)getParam('station', FALSE, 0));
 723: $outenc   = (string)getParam('charset', FALSE, INTERNAL_ENCODING);
 724: $start    = (int)getParam('start',    FALSE, DEF_START);
 725: $goal     = (int)getParam('goal',     FALSE, DEF_GOAL);
 726: $forecast = (int)getParam('forecast', FALSE, DEF_FORECAST);
 727: $columns  = COLUMNS;

URLパラメータを使って
jmaWeeklyWeather.php?id=1&forecast=1&station=49142
のようにすることで、ある地点の天気予報のみを表示させることができる。つまり、このスクリプトをホームページやブログの一部として組み込むことで、週間天気予報を表示するパーツになる。
station は予報地点コードで、予報地点情報ファイルを参照されたい。
また、出力はHTML文のみとなり、スタイルシートは本体ページの方で用意していただきたい。必要なclassは次の通り。

 280: <style>
 281: /* エラー表示 */
 282: p.werror {
 283:     color: red;
 284: }
 285: /* 天気予報表 */
 286: table.weather {
 287:     width: {$width}px;
 288:     border:solid 1px #000000;
 289:     border-collapse:collapse;
 290:     margin-top:10px;
 291: 
 292: }
 293: /* 天気予報表:月日表示部 */
 294: table.weather td.dt {
 295:     width: {$width}px;
 296:     border:solid 1px #000000;
 297:     border-collapse: collapse;
 298:     padding:4px;
 299:     white-space:nowrap;
 300:     text-align:center;
 301: }
 302: /* 天気予報表:予報表示部 */
 303: table.weather td.info {
 304:     width: {$width}px;
 305:     border:solid 1px #000000;
 306:     border-collapse: collapse;
 307:     padding:4px;
 308:     white-space:nowrap;
 309:     text-align:center;
 310: }
 311: /* 天気予報表:予報アイコン */
 312: img.wicon {
 313:     width: 60px;
 314: }
 315: /* 天気予報表:小さい文字 */
 316: span.wsmall {
 317:     font-size: small;
 318: }
 319: /* Tweetボタン */
 320: button.tweet_button {
 321:     color: white;
 322:     background-color: #1DA1F2;
 323:     font-size: 70%;
 324:     font-weight: bold;
 325:     text-align: center;
 326:     border-radius: 4px;
 327:     padding: 4px 8px 4px 8px;
 328:     border: none;
 329: }
 330: </style>

また、親となるホームページやブログの文字コードセットにあわせ、charset 変数で出力文字コードを変更できるようにした。たとえば
jmaWeeklyWeather.php?id=1&forecast=1&station=49142&charset=SJIS
とすると、甲府の週間天気予報をシフトJISで出力することができる。
URLパラメータを使って
jmaWeeklyWeather.php?region=02&pref=04
のようにすることで、あらかじめプルダウン選択を指定することができる。
ここでは、地方を「東北地方」(region=02)に、都道府県を「宮城県」(pref=04)に指定している。
URLパラメータを使って
jmaWeeklyWeather.php?start=1&goal=6
のように指定することで、予報開始日と終了日を指定することができる。
start は予報開始日(0:今日,1:明日‥‥6)、goal は予報終了日(0:今日,1:明日‥‥6)である。

予報地点情報ファイル

上述のように、予報地方と地点の関連を、あらかじめ予報地点情報ファイルとして用意しておく。予報地点の緯度・経度も用意しておくことで、「PHPで地図で指定した場所の天気予報を求める」のようにマップとの連携が容易になる。
予報地点情報ファイルは、同梱のプログラム jmaWeatherInit.php を使って自動生成することができる。
予報地点の緯度・経度は、気象庁の「地域気象観測システム(アメダス)」ページにある地域気象観測所一覧(ZIP圧縮形式)をダウンロードし、解凍して得られるCSVファイルから自動取得する。
予報地点情報ファイル(xml) jmaweatherspots date 適用日 update 作成日時 version バージョン spot code 電文コード VPFW50|VPFD51 page ページ番号 regionCode 地方コード regionName 地方名 prefCode 都道府県コード prefName 都道府県名 areaCode 予報地方コード areaName 予報地方名 stationCode 予報地点コード stationName 予報地点名 location 場所 latitude 緯度(世界測地系) longitude 経度(世界測地系)

 610: /**
 611:  * 指定した気象庁防災情報XMLから各地点の最新の府県週間天気予報情報URLを取得する.
 612:  * 気象庁防災情報XMLのデータコード$codeを指定し,URLを配列$itemsに格納する.
 613:  * @param   object $pwt    pahooWeatherオブジェクト
 614:  * @param   string $code   データコード
 615:  * @param   array  $items  府県週間天気予報情報URLを格納する配列
 616:  *                  [page]['url']  URL
 617:  *                  [page]['dt']   日時
 618:  * @param   string $errmsg エラーメッセージを格納
 619:  * @return  int 格納したURLの数/FALSE:エラー発生
 620: */
 621: function getWeatherUrls($pwt, $code, &$items, &$errmsg) {
 622:     //マッチするURLパターン
 623:     $pat1 = sprintf('/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)%s\_([0-9]+)\.xml/ui', $code);
 624: 
 625:     //気象庁防災情報XML:長期フィード - 定時配信
 626:     $errmsg = '';
 627:     unknown_certificate();
 628:     $xml = @simplexml_load_file($pwt::FEED_REGULAR_L);
 629:     //レスポンス・チェック
 630:     if (! isset($xml->entry)) {
 631:         $errmsg = '気象庁防災情報XMLにアクセスできません';
 632:         return FALSE;
 633:     }
 634: 
 635:     //フィードを解析
 636:     $items = array();
 637:     $cnt = 0;
 638:     foreach ($xml->entry as $entry) {
 639:         if (preg_match($pat1, (string)$entry->id, $arr> 0) {
 640:             $dt   = (string)$arr[1];
 641:             $page = (string)$arr[2];
 642:             //登録済み
 643:             if (isset($items[$page])) {
 644:                 //より新しければ要素を上書き
 645:                 if ($dt > $items[$page]['dt']) {
 646:                     $items[$page]['dt']  = (string)$dt;
 647:                     $items[$page]['url'] = (string)$entry->id;
 648:                 }
 649:             //未登録
 650:             } else {
 651:                 $items[$page]['dt']  = (string)$dt;
 652:                 $items[$page]['url'] = (string)$entry->id;
 653:                 $cnt++;
 654:             }
 655:         }
 656:     }
 657:     //ソート
 658:     ksort($items);
 659: 
 660:     return $cnt;
 661: }

jmaWeatherInit.php では、まず、長期フィード(定時配信)から VPFW50VPFD51 が含まれているURLを抽出し、ページ番号が重複しないよう配列 $items に格納する。

 663: /**
 664:  * 指定した府県週間天気予報情報URLから予報地点情報を求める.
 665:  * 府県週間天気予報情報URLを$urlに指定し,予報地点情報を配列$itemsに格納する.
 666:  * @param   string $url    府県週間天気予報情報URL
 667:  * @param   array  $items  予報地点情報を格納する配列
 668:  * @param   string $errmsg エラーメッセージを格納
 669:  * @return  int 格納した予報地点の数/FALSE:エラー発生
 670: */
 671: function getWeatherSpotsSub($url, &$items, &$errmsg) {
 672:     $errmsg = '';
 673:     unknown_certificate();
 674:     $xml = @simplexml_load_file($url);
 675:     //レスポンス・チェック
 676:     if (! isset($xml->Body->MeteorologicalInfos)) {
 677:         $errmsg = '府県週間天気予報情報を取得できません';
 678:         return FALSE;
 679:     }
 680: 
 681:     $flag1 = $flag2 = FALSE;
 682:     foreach ($xml->Body->MeteorologicalInfos as $MeteorologicalInfos) {
 683:         //地方名
 684:         if (!$flag1 && $MeteorologicalInfos['type'] == '区域予報') {
 685:             $flag1 = TRUE;
 686:             $cnt = 0;
 687:             foreach ($MeteorologicalInfos->TimeSeriesInfo->Item as $item) {
 688:                 $items[$cnt]['areaName'] = (string)$item->Area->Name;
 689:                 $items[$cnt]['areaCode'] = (string)$item->Area->Code;
 690:                 $cnt++;
 691:             }
 692:         //予報地点名
 693:         } else if (!$flag2 && $MeteorologicalInfos['type'] == '地点予報') {
 694:             $flag2 = TRUE;
 695:             $cnt = 0;
 696:             foreach ($MeteorologicalInfos->TimeSeriesInfo->Item as $item) {
 697:                 $items[$cnt]['stationName'] = (string)$item->Station->Name;
 698:                 $items[$cnt]['stationCode'] = (string)$item->Station->Code;
 699:                 $cnt++;
 700:             }
 701:         }
 702:     }
 703:     //余分な予報地点を削除
 704: /**
 705:     foreach ($items as $key=>$item) {
 706:         if ($key >= $cnt) {
 707:             unset($items[$key]['stationName']);
 708:             unset($items[$key]['stationCode']);
 709:         }
 710:     }
 711: **/
 712: 
 713:     return $cnt;
 714: }

取得したURLを引数に getWeatherSpotsSub を実行し、情報XMLにある要素 areaNameareaCodestationNamestationCode を取り出して配列に格納する。

 764: /**
 765:  * 予報地点情報に場所・緯度・経度を代入する:VPFD51用
 766:  * @param   array  $amedas 地域観測所一覧
 767:  * @param   array  $spot   予報地点情報
 768:  * @return  なし
 769: */
 770: function addLocation($amedas, &$spot) {
 771:     //地域観測所を探索
 772:     foreach ($amedas as $item) {
 773:         if ($spot['stationCode'] == $item['stationCode']) {
 774:             $spot['location']  = $item['location'];
 775:             $spot['latitude']  = $item['latitude'];
 776:             $spot['longitude'] = $item['longitude'];
 777:             return;
 778:         }
 779:     }
 780: }

すべてのページ番号の情報を取り出したら、次に addLocation を実行し、地域観測所一覧と予報地点名を比較し、緯度・経度を配列に追加する。

 744: /**
 745:  * 予報地点情報に地方名を代入する:VPFD51用
 746:  * @param   array $spot 予報地点情報
 747:  * @return  なし
 748: */
 749: function addAreaName(&$spot) {
 750:     global $TableStation;
 751: 
 752:     foreach ($TableStation as $pref=>$arr1) {
 753:         foreach ($arr1 as $areaName=>$arr2) {
 754:             foreach ($arr2 as $stationName) {
 755:                 if (($spot['prefName'] == $pref&& ($spot['stationName'] == $stationName)) {
 756:                     $spot['areaName'] = (string)$areaName;
 757:                     return;
 758:                 }
 759:             }
 760:         }
 761:     }
 762: }

なお、天気予報(VPFD51 のみ取得)の場合、予報地点と予報地方をリンクさせる情報が見当たらない。これがないと、天気予報・降水確率を取得することができない。そこで、気象庁天気予報ページから手作業で対応表 $TableStation を起こした。
予報地点名から地方名を逆引きして配列に代入する関数が addAreaName である。

主な変更点:バージョン1.x→2.x

表示はバージョン1.xに近づけるようにしているが、関数やメソッドが大幅に変わっており、ご注意いただきたい。

jmaWeeklyWeather.php
  • 表示用CSSは関数 makeHeader に集約した。
  • 天気予報の種類を定数 DEF_FORECAST で指定。従来の週間予報は予報地点が約70箇所と少ない、という声をいただき、約170箇所ある2~3日予報と選べるようにした。
  • 地方セレクタ(北海道地方,東北地方‥‥)を使うかどうかを定数 REGION で指定。
  • 地方・都道府県・予報地点のセレクタは、動作を軽くするため、JavaScriptで実装する形に変更した。
pahooWeather.php
  • 週間予報を表示するために、その都度、気象庁防災情報XMLの3つのXMLファイルを読み込むように変更した。
  • とくに定数 FEED_REGULAR_L で指定するXMLファイルが大きく、もし読み込みエラーや動作がもっさりするようであれば、状況を添えてお問い合わせいただきたい。
jmaweatherspots.xml
  • データ構造を変更したため、差し替えること。
jmaWeatherInit.php
  • データ構造を変更したため、差し替えること。

活用例

《全国の天気》今日・明日の天気と全国概況一覧 & 週間予報」(みんなの知識 ちょっと便利帳)では、このサンプル・プログラムを活用し、都市名を検索しやすいページを提供している。
また、サイドバーにコンパクトな形で天気予報を掲示している。
ご活用いただき、ありがとうございます。

質疑応答

【質問】 しゅんすけ様
天気予報プログラムのプルダウンから地域変更しても表示がされないようですが。
【回答】
ホームページのサンプル・プログラムは表示します。
ご自身のプログラムが正常に動かないときは、キャッシュディレクトリ(定数 DIR_CACHE で指定するもの)は、ディレクトリごと消去してから走らせてみてください。キャッシュに古い(http)URLが残っている場合があるためです。
また、予報地点が更新されました。また、予報地点ファイル(定数 FILE_JMASPOTS で指定するもの)を差し替えてください。


【質問】 しゅんすけ様
天気予報プログラムを実行したところ下記のエラーが表示されました。
エラー:気象庁防災情報XMLから週間天気予報情報を取得できません
【回答】
気象庁防災情報XMLがhttps化したためでした。
対応プログラムをアップしました。クラスファイル "[pahooWeather.php:bleu]" を差し替えれば動くようになります。お試しください。


【質問】
https://www.benricho.org/weather_japan/ のサイトように特定の地域(例えば東京都・東京)の天気のみを表示させるにはどのように記述すればよいのですか?
【回答】
URLパラメータの station(予報地点コード)に整数を設定して呼び出してください。たとえば東京都・東京であれば、下記のようにして呼び出します。
 jmaWeeklyWeather.php?station=44132
また、プログラムに直接値を代入したいのであれば、メイン・プログラムの初期化にある $pref, $city に直接値を代入してください。→「解説:表示とURLパラメータ」参照


【質問】
pahooWeather.php の 583~589行目(// 月の補正 の部分)を下記のように修正したところ月日は一致したのですが、今日の天気(当日の天気)が出てきません。
date_add($dt, date_interval_create_from_date_string('-1 month'));
           ↓
date_add($dt, date_interval_create_from_date_string('1 day'));
【回答】
"pahooWeather.php" version 4.18 では、当該行はコメントアウトしており機能していません。ご確認ください。


【質問】
サイト内に天気予報と今日は何の日(https://www.pahoo.org/e-soul/webtech/php05/php05-16-01.shtm)とを同時に表示さたところPHPで競合が起こりエラーが発生してしまいました。これらを解決する良い方法があれば教えてください。
【回答】
「競合」というのは、具体的にどのようなことが起きているのか、表示されるメッセージなどをお知らせください。


【質問】
サイト内に天気予報と今日は何の日を同時に表示させた際のエラーは次のようなものでした。
Cannot redeclare ~ (previously declared in ~)in ~
【回答】
サイト内で、"jmaWeeklyWeather.php" と "DayToday.php" をマージするか、includeしていませんか。この2つプログラムは別々に配置することを想定しています。


【質問】
天気予報が表示されなくなりました。

気象庁の表示が変わったためと思われますが、全国の天気予報(https://www.jma.go.jp/bosai/forecast/)からjmaweatherspots.xmlの書き換えを行うことで表示することは可能でしょうか。
【回答】
冒頭に記載の通り、2021年(令和3年)2月24日、気象庁ホームページがリニューアルしたため、pahooWeather::__jma_readWeeklyWeather() メソッドが機能していません。jmaweatherspots.xml の書き換えだけでは対応できません。
プログラムを全面的に書き直しました。「主な変更点:バージョン1.x→2.x」をご覧いただき、ご利用ください。


【質問】
PHPで天気予報を求める(3)で、指定した予報地点の天気の画像が実際の気象庁の予報地点の画像と合っていない地点が多くあります。最高温度と最低温度は指定した予想地点と気象庁の予報地点とは完全一致しています。

画像が合っていないのは、何らかのループ処理がなされ、その結果、つまり最後の値が読み込まれるために、対象地域の最後の地点の画像が読み込まれているようです。例えば、長野県であれば、長野市、松本市、飯田市の3地点がありますが、ループ処理の結果、長野市の画像が最後の地点(飯田市)の画像に置き換わっているようです。これは長野県に限らず全国的にそのような現象が見られます。

気象庁ホームページがリニューアルしたため、pahooWeather::__jma_readWeeklyWeather() メソッドが機能しなくなりました。

との記載がありますが、修正にはかなりの時間がかかるとのことでしょうか。
【回答】
冒頭に記載の通り、プログラムを全面的に書き直しました。「主な変更点:バージョン1.x→2.x」をご覧いただき、ご利用ください。


【質問】
「PHPで天気予報を求める」で3月6日の修正版のリリース有難うございます。大変助かっております。

3日間の天気予報(週間予報ではない)において、深夜0時以降、前日、その日、その翌日の天気の表示となりますが、その日の天気情報が前日の天気情報として表示されてしまいます。言い換えれば、例えば3月20日の天気情報が、3月19日の情報として表示されているようです。

ただ、午前5時以降は、気象庁の最新の情報を拾うので、表示は正しくなります。つまり深夜0時から午前5時までの表示に問題があるように思われます。

深夜0時以降、日付が自動的に次の日に変われば、問題は解消されるのかと思われますが、そのような対応で根本的に解決されるのか、そこは検証していただければ幸いです。
【回答】
冒頭にあるとおり、改良版をリリースしました。お試しください。


【質問】
修正されたとのメッセージはいただきましたが、本日3月21日深夜0時以降に確認した所、以下のことが分かりました。

週間予報の方は、深夜0時以降は新しい日付に切り替わるために正しく表示されますが、天気予報の方は、天気情報は翌日分が正しく表示されますが、日付が前日のままとなっているため、日付と天気情報が合わなくなったままです。ですから、深夜0時に日付が変わった段階で、次の日に切り替わった日付が表示されれば、天気情報と合うために問題が解消される思うのですが。

恐れ入りますが、修正をお願いできますでしょうか。
【回答】
調べたところ、時間帯によるものではなく、次の不具合によるようです。

札幌、長崎など複数の地点は、VPFW51(府県週間天気予報)とVPFD51(府県天気予報 R1)とで割り当てられている地方コード(areaCode)が異なっており、このため違う地方のデータを取り込んでしまっていました。
違いを正しく処理するよう、"pahooWeather.php" を改良しました。「v.5.03」を記した箇所です。
また、参照している地域観測所一覧(アメダス)に「福島」が載っていませんでした。このため、福島の天気が会津のデータを読み込んでいました。あたらしい地域観測所一覧をダウンロードし、"jmaweatherspots.xml" を再作成しました。
お試しください。

参考サイト

(この項おわり)
header