PHPで天気予報を求める

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

(2021 年 4 月 8 日)pahoo_simplexml_load を pahooCache::simplexml_load へ
(2021 年 4 月 2 日)キャッシュ・システム導入:pahooCache クラス,"jmaweatherspots.xml" を更新.
(2021 年 3 月 28 日)当日~2 日後の天気予報が違う都市になる不具合を修正.予報値地点情報に「福島」が載っていなかったため,別の地点の天気予報を拾ってきていた不具合を修正.
(2021 年 3 月 15 日)取得時刻によって,情報無し項目がゼロになったり,表示日がズレる不具合を修正
(2021 年 3 月 7 日)気温等の値が 0 のとき、PHP7 以下で非表示となる不具合を修正。
(2021 年 3 月 5 日)2021 年 2 月 24 日の気象庁サイト・リニューアルにより、スクレイピングによる取り出しが難しくなったため、気象庁防災情報 XML からの取得に変更した。

プログラムを実行する

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

PHPで天気予報を求める

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

圧縮ファイルの内容
jmaWeeklyWeather.phpサンプル・プログラム本体。
pahooWeather.php気象情報に関わるクラス pahooWeather。
気象情報に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
jmaweatherspots.xml予報地点情報ファイル。「PHPで天気予報を求める」参照。
jmaWeatherInit.php予報地点情報ファイル作成プログラム。「PHPで天気予報を求める」参照。

目次

気象庁防災情報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 の命名規則は以下の通り。

http://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 予報地点コード

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

0049: //表示幅(ピクセル)
0050: define('WIDTH', 700);
0051: 
0052: //地方セレクタ(北海道地方,東北地方‥‥)を使うかどうか
0053: // 0 = 使わない
0054: // 1 = 使う
0055: define('REGION', 1);
0056: 
0057: //天気予報の種類(初期値)
0058: // 0 = 天気予報
0059: // 1 = 週間予報
0060: define('DEF_FORECAST', 1);
0061: 
0062: //天気予報の表示列数
0063: define('COLUMNS', 7);
0064: 
0065: //予報の開始日:デフォルト値(0:今日,1:明日‥‥6)
0066: define('DEF_START', 0);
0067: 
0068: //予報の終了日:デフォルト値(0:今日,1:明日‥‥6)
0069: 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 ファイルを保持しておきキャッシュ・システムを導入することにする。

0074: //キャッシュ保持時間(分) 0:キャッシュしない
0075: //気象庁へのアクセス負荷軽減のため,60分以上のキャッシュ保持をお勧めします.
0076: define('LIFE_CACHE', 120);
0077: 
0078: //キャッシュ・ディレクトリ
0079: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
0080: //最大150Mバイトを消費します.
0081: 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 バイト
となる。

0023: /**
0024:  * コンストラクタ
0025:  * @param int    $life キャッシュ保持時間(分)(省略可能)
0026:  * @param string $dir キャッシュ・ディレクトリ(省略可能)
0027:  * @return なし
0028: */
0029: function __construct($life=self::LIFE_CACHE$dir=self::DEF_DIRCACHE) {
0030:     if ($life < 0) {
0031:         $life = 0;
0032:     }
0033:     if (preg_match('/\/$/ui', $dir) == 0) {
0034:         $dir = $dir . '/';
0035:     }
0036:     $this->error      = FALSE;
0037:     $this->errmsg     = '';
0038:     $this->debug      = '';
0039:     $this->lifeCache = $life;
0040:     $this->dirCache  = $dir;
0041: 
0042:     //PHPバージョンチェック
0043:     if (! isphp5over()) {
0044:         $this->error   = TRUE;
0045:         $this->errmsg  = '動作にはPHP5以上が必要です';
0046:         return;
0047:     }
0048: 
0049:     //キャッシュ・ディレクトリが無ければ作成
0050:     if (! is_dir($this->dirCache)) {
0051:         $res = mkdir($this->dirCache, 0744);
0052:         if ($res == FALSE) {
0053:             $this->error   = TRUE;
0054:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
0055:             return;
0056:         }
0057:     }
0058: }

キャッシュ・システムは、pahooCache クラス に分離してある。

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

0094: /**
0095:  * キャッシュの削除
0096:  * @param string $pat   削除するファイル名(正規表現指定可能)(省略可能)
0097:  * @param int    $life  キャッシュ保持時間(分)(省略可能)
0098:  * @return int 削除したファイル数
0099: */
0100: function delete($pat='.+', $life=self::LIFE_CACHE) {
0101:     $fullname = $this->dirCache . '*';
0102: 
0103:     $cnt = 0;
0104:     foreach (glob($fullnameas $name) {
0105:         //ファイルかつパターンにマッチしたファイルが削除対象
0106:         if (is_file($name) && (preg_match($patbasename($name)) > 0)) {
0107:             $ft = filemtime($name);
0108:             //$lifeより古いファイルが削除対象
0109:             if ((time() - $ft) > $life * 60) {
0110:                 if (unlink($name) == TRUE) {
0111:                     $cnt++;
0112:                 }
0113:             }
0114:         }
0115:     }
0116: 
0117:     return $cnt;
0118: }

0120: /**
0121:  * cURLによるコンテンツ取得
0122:  * @param string $url URL
0123:  * @return mixed コンテンツ/FALSE:読み込み失敗
0124: */
0125: function cLoad($url) {
0126:     $curl = curl_init($url);
0127:     curl_setopt($curlCURLOPT_HEADERFALSE);
0128:     curl_setopt($curlCURLOPT_RETURNTRANSFERTRUE);
0129:     curl_setopt($curlCURLOPT_SSL_VERIFYPEERFALSE);   //サーバ証明書検証をスキップ
0130:     curl_setopt($curlCURLOPT_SSL_VERIFYHOSTFALSE);   //  〃
0131:     $contents = curl_exec($curl);
0132:     if (($contents == FALSE|| (curl_errno($curl) != 0)) {
0133:         $this->error   = TRUE;
0134:         $this->errmsg  = '"' . $url . '" が取得できません';
0135:         return FALSE;
0136:     }
0137:     $arr = curl_getinfo($curl);
0138:     if ($arr['http_code'] != 200) {
0139:         $this->error   = TRUE;
0140:         $this->errmsg  = '"' . $url . '" が取得できません';
0141:         return FALSE;
0142:     }
0143:     curl_close($curl);
0144: 
0145:     return $contents;
0146: }

0148: /**
0149:  * ネットからコンテンツを読み込む
0150:  * @param string $url   URL
0151:  * @param string $fname キャッシュ・ファイル名(フルパス)
0152:  * @return mixed コンテンツ/FALSE:読み込み失敗
0153: */
0154: function forceLoad($url) {
0155:     $fname = $this->dirCache . md5($url);  //キャッシュするファイル名
0156: 
0157:     $res = $this->cLoad($url);
0158:     if ($res == FALSE) {
0159:         return FALSE;
0160:     }
0161:     $ret = file_put_contents($fname$res);
0162:     if ($ret == FALSE) {
0163:         $this->error = TRUE;
0164:         $this->errmsg = 'キャッシュファイル "' . $fname . '" の書き込み失敗しました';
0165:         return FALSE;
0166:     }
0167:     $this->debug .= $url . "\n";
0168: 
0169:     return $res;
0170: }

0172: /**
0173:  * コンテンツを読み込む
0174:  * @param string $url URL
0175:  * @return mixed コンテンツ/FALSE:読み込み失敗
0176: */
0177: function load($url) {
0178:     //キャッシュ有効
0179:     if ($this->lifeCache > 0) {
0180:         $this->delete('/[0-9a-f]+/ui', $this->lifeCache);    //古いキャッシュを削除
0181:         $fname = $this->dirCache . md5($url);  //キャッシュするファイル名
0182:         //キャッシュが存在する
0183:         if (is_file($fname)) {
0184:             $res = file_get_contents($fname);
0185:             if ($res == FALSE) {
0186:                 $this->error = TRUE;
0187:                 $this->errmsg = 'キャッシュファイル "' . $fname . '" の読み込みに失敗しました';
0188:                 return FALSE;
0189:             }
0190:             $this->debug .= $fname . "\n";
0191:         //ネットから取得
0192:         } else {
0193:             $res = $this->forceLoad($url);
0194:         }
0195: 
0196:     //キャッシュ無効
0197:     } else {
0198:         $res = $this->forceLoad($url);
0199:     }
0200: 
0201:     return $res;
0202: }

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

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

0204: /**
0205:  * キャッシュ・システムを利用したSimple XMLロード
0206:  * @param string $url XMLファイル名(URL指定)
0207:  * @return bool TRUE:取得成功/FALSE:失敗
0208: */
0209: function simplexml_load($url) {
0210:     //キャッシュ・システムを使ったロード
0211:     $contents = $this->load($url);
0212: 
0213:     //失敗したらネットから強制ロード
0214:     if ($this->iserror()) {
0215:         $this->error = FALSE;
0216:         $this->errmsg = '';
0217:         $contents = $this->forceLoad($url);
0218:         if ($this->iserror()) {
0219:             return FALSE;
0220:         }
0221:     }
0222: 
0223:     //XMLロード
0224:     $xml = @simplexml_load_string($contents);
0225: 
0226:     //キャッシュが壊れている可能性
0227:     if ($xml == FALSE) {
0228:         $contents = $this->forceLoad($url);
0229:         $xml = @simplexml_load_string($contents);
0230:         if ($xml == FALSE) {
0231:         $this->error   = TRUE;
0232:         $this->errmsg  = '"' . $url . '" が取得できません';
0233:             return FALSE;
0234:         }
0235:     }
0236: 
0237:     return $xml;
0238: }

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

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

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

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

0103: /**
0104:  * 予報地点情報を読み込む
0105:  * @param なし
0106:  * @return bool TRUE:読込成功/FALSE:失敗
0107: */
0108: function readJmaSpots() {
0109:     $cnt = 0;
0110: 
0111:     $xml = @simplexml_load_file(self::FILE_JMASPOTS);
0112:     //レスポンス・チェック
0113:     if (isset($xml->spot) == FALSE) {
0114:         $res = FALSE;
0115:             $this->error = TRUE;
0116:         $this->errmsg = '予報地点ファイル "' . self::FILE_JMASPOTS . '" がありません';
0117:     //バージョン・チェック
0118:     } else if ($xml->version != self::FILE_VERSION) {
0119:         $res = FALSE;
0120:             $this->error = TRUE;
0121:         $this->errmsg = '予報地点ファイル "' . self::FILE_JMASPOTS . '" のバージョンが違います';
0122:     } else {
0123:         //必要な情報を配列へ格納
0124:         foreach ($xml->spot as $spot) {
0125:             $this->spots[$cnt]['code']        = (string)$spot->code;
0126:             $this->spots[$cnt]['page']        = (string)$spot->page;
0127:             $this->spots[$cnt]['regionCode']  = (string)$spot->regionCode;
0128:             $this->spots[$cnt]['regionName']  = (string)$spot->regionName;
0129:             $this->spots[$cnt]['prefCode']    = (string)$spot->prefCode;
0130:             $this->spots[$cnt]['prefName']    = (string)$spot->prefName;
0131:             $this->spots[$cnt]['areaCode']    = (string)$spot->areaCode;
0132:             $this->spots[$cnt]['areaName']    = (string)$spot->areaName;
0133:             $this->spots[$cnt]['stationName'] = (string)$spot->stationName;
0134:             $this->spots[$cnt]['stationCode'] = (string)$spot->stationCode;
0135:             $this->spots[$cnt]['longitude']   = (float)$spot->longitude;
0136:             $this->spots[$cnt]['latitude']    = (float)$spot->latitude;
0137:             $this->spots[$cnt]['location']    = (float)$spot->location;
0138:             $cnt++;
0139:         }
0140:         if ($cnt == 0) {
0141:             $res = FALSE;
0142:             $this->error = TRUE;
0143:             $this->errmsg = '予報地点ファイル "' . self::FILE_JMASPOTS . '" がありません';
0144:         } else {
0145:             $res = TRUE;
0146:         }
0147:     }
0148:     return $res;
0149: }

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

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

0333: /**
0334:  * 気象庁防災情報XMLから最新の天気予報情報URLを取得
0335:  * @param int $page  ページ番号
0336:  * @return array(VPFD51, VPFW50)/FALSE:取得失敗
0337: */
0338: function jmaGetWeatherForecastURL($page) {
0339:     //URLパターン
0340:     $vpfd51 = sprintf('/http\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VPFD51\_%06d\.xml/ui', $page);
0341:     $vpfw50 = sprintf('/http\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VPFW50\_%06d\.xml/ui', $page);
0342: 
0343:     $xml = $this->pcc->simplexml_load(self::FEED_REGULAR_L);
0344:     //レスポンス・チェック
0345:     if ($this->pcc->iserror()) {
0346:         $this->error  = TRUE;
0347:         $this->errmsg = $this->pcc->errmsg;
0348:         return FALSE;
0349:     } else if (! isset($xml->entry)) {
0350:         $this->error  = TRUE;
0351:         $this->errmsg = '"' . self::FEED_REGULAR_L . '" の解釈に失敗しました';
0352:         return FALSE;
0353:     }
0354:     $this->xmlfile[0] = self::FEED_REGULAR_L;
0355: 
0356:     //フィード(XMLファイル)解析
0357:     $vpfd51_url = $vpfd51_dt = '';
0358:     $vpfw50_url = $vpfw50_dt = '';
0359:     $res = FALSE;
0360:     foreach ($xml->entry as $node) {
0361:         //日時がより新しいURLを採用
0362:         if (preg_match($vpfd51$node->id$arr) > 0) {
0363:             if ($arr[1] > $vpfd51_dt) {
0364:                 $vpfd51_url = $arr[0];
0365:                 $vpfd51_dt  = $arr[1];
0366:                 $res = TRUE;
0367:             }
0368:         } else if (preg_match($vpfw50$node->id$arr) > 0) {
0369:             if ($arr[1] > $vpfw50_dt) {
0370:                 $vpfw50_url = $arr[0];
0371:                 $vpfw50_dt  = $arr[1];
0372:                 $res = TRUE;
0373:             }
0374:         }
0375:     }
0376: 
0377:     //エラー・チェック
0378:     if (! $res) {
0379:         $this->error  = TRUE;
0380:         $this->errmsg = '気象庁防災情報XMLから週間天気予報情報を取得できません';
0381:         return FALSE;
0382:     }
0383: 
0384:     $this->xmlfile[1] = $vpfw50_url;
0385:     $this->xmlfile[2] = $vpfd51_url;
0386: 
0387:     return array($vpfd51_url$vpfw50_url);
0388: }

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

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

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

0390: /**
0391:  * 気象庁防災情報XMLから天気予報情報を読み込む
0392:  * @param array  $items    天気予報を格納する配列
0393:  * @param string $station  予報地点コード
0394:  * @param int    $forecast 0:天気予報,1:週間天気予報(省略時:1)
0395:  *                  0のとき‥‥jmaWeeklyWeather[0](本日)~[6](6日後)に代入
0396:  *                  1のとき‥‥jmaWeeklyWeather[0](本日)~[2](2日後)に代入
0397:  * @return bool TRUE:成功/FALSE:失敗
0398: */
0399: function jmaGetWeatherForecast(&$items$station$forecast=1) {
0400:     //電文コード
0401:     $code = ($forecast == 0) ? 'VPFD51' : 'VPFW50';
0402:     //名前空間
0403:     define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
0404:     //曜日
0405:     static $week_name = array('', '', '', '', '', '', '');
0406:     //マッチングパターン
0407:     $pat11 = '/([0-9]+)\-([0-9]+)\-([0-9]+)/ui';                //年月日
0408:     $pat12 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:/ui';        //年月日時
0409:     $pat21 = '/から/ui';                                      //降水確率
0410:     $pat31 = '/最低気温/ui';                                  //最低気温
0411:     $pat32 = '/^日中の最高気温/ui';                              //最高気温
0412: 
0413:     //予報地点コードの取得
0414:     $res = FALSE;
0415:     foreach ($this->spots as $id=>$spot) {
0416:         if (($spot['code'] == $code) && ($spot['stationCode'] == $station)) {
0417:             $res = TRUE;
0418:             break;
0419:         }
0420:     }
0421:     if ($res == FALSE) {
0422:         $this->error  = TRUE;
0423:         $this->errmsg = '予報地点コードが見つかりません';
0424:         return FALSE;
0425:     }
0426: 
0427:     //初日のみ初期化
0428:     $i = 0;
0429:     $dt = new DateTime(date('Y-m-d'));
0430:     $this->jmaWeeklyWeather[$i]['year']  = date('Y');
0431:     $this->jmaWeeklyWeather[$i]['month'] = date('n');
0432:     $this->jmaWeeklyWeather[$i]['day']   = date('j');
0433:     $this->jmaWeeklyWeather[$i]['week']  = (string)$week_name[date_format($dt, 'w')];
0434:     $this->jmaWeeklyWeather[$i]['stationName'] = $this->spots[$id]['stationName'];
0435:     $this->jmaWeeklyWeather[$i]['weather']  = '';
0436:     $this->jmaWeeklyWeather[$i]['image']    = '';
0437:     $this->jmaWeeklyWeather[$i]['rainy']    = '';
0438:     $this->jmaWeeklyWeather[$i]['temp_max'] = '';
0439:     $this->jmaWeeklyWeather[$i]['temp_min'] = '';
0440: 
0441:     //最新の週間天気予報情報URLを取得
0442:     $page = $this->spots[$id]['page'];
0443:     $area = $this->spots[$id]['areaCode'];
0444:     $station = $this->spots[$id]['stationCode'];
0445: 
0446:     list($vpfd51$vpfw50) = $this->jmaGetWeatherForecastURL($page);
0447:     if ($this->pcc->iserror()) {
0448:         $this->error  = TRUE;
0449:         $this->errmsg = $this->pcc->geterror();
0450:         return FALSE;
0451:     }
0452:     if ($this->error)        return FALSE;
0453:     if ($this->pcc->error)    return FALSE;
0454: 
0455:     $this->jmaWeeklyWeather['stationCode']  = $this->spots[$id]['stationCode'];
0456:     $this->jmaWeeklyWeather['stationName']  = $this->spots[$id]['stationName'];
0457:     $this->jmaWeeklyWeather['location']     = $this->spots[$id]['location'];
0458: 
0459:     //VPFW51(府県週間天気予報)の解析
0460:     if ($forecast == 1) {
0461:         $xml = $this->pcc->simplexml_load($vpfw50);
0462:         //レスポンス・チェック
0463:         if ($this->pcc->iserror()) {
0464:             $this->error  = TRUE;
0465:             $this->errmsg = $this->pcc->errmsg;
0466:             return FALSE;
0467:         } else if (! isset($xml->Body->MeteorologicalInfos)) {
0468:             $this->error  = TRUE;
0469:             $this->errmsg = '"' . $vpfw50 . '" の解釈に失敗しました';
0470:             return FALSE;
0471:         }
0472: 
0473:         //年月日取得
0474:         foreach ($xml->Body->MeteorologicalInfos->TimeSeriesInfo->TimeDefines->TimeDefine as $TimeDefine) {
0475:             if (preg_match($pat11$TimeDefine->DateTime$arr) > 0) {
0476:                 //最初の情報は何時か
0477:                 $i = (int)$TimeDefine['timeId'];
0478:                 if ($i == 1) {
0479:                     //$dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
0480:                     //v.5.02
0481:                     $ss2 = date('Y-m-d');
0482:                     if ($ss2 < $arr[0])         $dd = 0;     //明日
0483:                     else if ($ss2 == $arr[0])   $dd = (-1);       //今日
0484:                     else                        $dd = (-2);       //昨日
0485:                 }
0486:                 $i += $dd;
0487:                 if ($i < 0)     continue;    //v.5.02
0488:                 //予報1日分の初期化
0489:                 $dt = new DateTime($arr[0]);
0490:                 $this->jmaWeeklyWeather[$i]['year']  = (int)$arr[1];
0491:                 $this->jmaWeeklyWeather[$i]['month'] = (int)$arr[2];
0492:                 $this->jmaWeeklyWeather[$i]['day']   = (int)$arr[3];
0493:                 $this->jmaWeeklyWeather[$i]['week']  = (string)$week_name[date_format($dt, 'w')];
0494:                 $this->jmaWeeklyWeather[$i]['weather']  = '';
0495:                 $this->jmaWeeklyWeather[$i]['image']    = '';
0496:                 $this->jmaWeeklyWeather[$i]['rainy']    = '';
0497:                 $this->jmaWeeklyWeather[$i]['temp_max'] = '';
0498:                 $this->jmaWeeklyWeather[$i]['temp_min'] = '';
0499:             }
0500:         }
0501: 
0502:         foreach ($xml->Body->MeteorologicalInfos as $info) {
0503:             //天気・降水確率の取得
0504:             if ((string)$info['type'] == '区域予報') {
0505:                 foreach ($info->TimeSeriesInfo->Item as $item) {
0506:                     if ((string)$item->Area->Code != $area)        continue;
0507:                     foreach ($item->Kind as $kind) {
0508:                         if ((string)$kind->Property->Type == '天気') {
0509:                             $node = $kind->Property->WeatherPart->children(JMX_EB);
0510:                             foreach ($node->Weather as $Weather) {
0511:                                 $id = (int)$Weather->attributes()->refID + $dd;
0512:                                 if ($id < 0)    continue;    //v.5.02
0513:                                 $this->jmaWeeklyWeather[$id]['weather'] = $this->jmaShortWeather((string)$Weather);
0514:                             }
0515:                             $node = $kind->Property->WeatherCodePart->children(JMX_EB);
0516:                             foreach ($node->WeatherCode as $WeatherCode) {
0517:                                 $id = (int)$WeatherCode->attributes()->refID + $dd;
0518:                                 if ($id < 0)    continue;    //v.5.02
0519:                                 //当日夜間
0520:                                 if (($id == 0) && (date('H') >= 18)) {
0521:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jma_telop2url((int)$WeatherCode, 1);
0522:                                 } else {
0523:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jma_telop2url((int)$WeatherCode, 0);
0524:                                 }
0525:                             }
0526:                         } else if ((string)$kind->Property->Type == '降水確率') {
0527:                             $node = $kind->Property->ProbabilityOfPrecipitationPart->children(JMX_EB);
0528:                             foreach ($node->ProbabilityOfPrecipitation as $rainy) {
0529:                                 $id = (int)$rainy->attributes()->refID + $dd;
0530:                                 if ($id < 0)    continue;    //v.5.02
0531:                                 $this->jmaWeeklyWeather[$id]['rainy'] = (string)$rainy;
0532:                             }
0533:                         }
0534:                     }
0535:                 }
0536:             //最低気温・最高気温
0537:             } else if ((string)$info['type'] == '地点予報') {
0538:                 foreach ($info->TimeSeriesInfo->Item as $item) {
0539:                     if ((int)$item->Station->Code != $stationcontinue;
0540:                     foreach ($item->Kind->Property as $property) {
0541:                         if ((string)$property->Type == '最低気温') {
0542:                             $node = $property->TemperaturePart->children(JMX_EB);
0543:                             foreach ($node->Temperature as $Temperature) {
0544:                                 $id = (int)$Temperature->attributes()->refID + $dd;
0545:                                 if ($id < 0)    continue;    //v.5.02
0546:                                 $this->jmaWeeklyWeather[$id]['temp_min'] = (string)$Temperature;   //v.5.02
0547:                             }
0548:                         } else if ((string)$property->Type == '最高気温') {
0549:                             $node = $property->TemperaturePart->children(JMX_EB);
0550:                             foreach ($node->Temperature as $Temperature) {
0551:                                 $id = (int)$Temperature->attributes()->refID + $dd;
0552:                                 if ($id < 0)    continue;    //v.5.02
0553:                                 $this->jmaWeeklyWeather[$id]['temp_max'] = (string)$Temperature;   //v.5.02
0554:                             }
0555:                         }
0556:                     }
0557:                 }
0558:             }
0559:         }
0560:     }
0561: 
0562:     //VPFD51(府県天気予報 R1)の解析
0563:     //v.5.03修正(ここから)
0564:     $code = 'VPFD51';
0565:     //予報地点コードの取得
0566:     $res = FALSE;
0567:     foreach ($this->spots as $id=>$spot) {
0568:         if (($spot['code'] == $code) && ($spot['stationCode'] == $station)) {
0569:             $res = TRUE;
0570:             break;
0571:         }
0572:     }
0573:     if ($res == FALSE) {
0574:         $this->error  = TRUE;
0575:         $this->errmsg = '予報地点コードが見つかりません';
0576:         return FALSE;
0577:     }
0578:     $area = $this->spots[$id]['areaCode'];
0579:     //v.5.03修正(ここまで)
0580: 
0581:     $xml = $this->pcc->simplexml_load($vpfd51);
0582:     //レスポンス・チェック
0583:     if ($this->pcc->iserror()) {
0584:         $this->error  = TRUE;
0585:         $this->errmsg = $this->pcc->errmsg;
0586:         return FALSE;
0587:     } else if (! isset($xml->Body->MeteorologicalInfos)) {
0588:         $this->error  = TRUE;
0589:         $this->errmsg = '"' . $vpfd51 . '" の解釈に失敗しました';
0590:         return FALSE;
0591:     }
0592: 
0593:     //初期化
0594:     $rain_table = array('-', '-', '-', '-', '-', '-', '-', '-');
0595:     $temp_table = array(0, 0, 0, 0);
0596: 
0597:     foreach ($xml->Body->MeteorologicalInfos as $info) {
0598:         if ((string)$info['type'] == '区域予報') {
0599:             foreach ($info->TimeSeriesInfo as $TimeSeriesInfo) {
0600:                 //日時
0601:                 foreach ($TimeSeriesInfo->TimeDefines->TimeDefine as $TimeDefine) {
0602:                     if (preg_match($pat21$TimeDefine->Name$arr) > 0) {
0603:                         preg_match($pat12$TimeDefine->DateTime$arr);
0604:                         $ss1 = $arr[1] . '-' . $arr[2] . '-' . $arr[3];
0605:                         //最初の情報は何時か
0606:                         $i = (int)$TimeDefine['timeId'];
0607:                         if ($i == 1) {
0608:                         //  $dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
0609:                             //v.5.02
0610:                             $ss2 = date('Y-m-d');
0611:                             if ($ss2 < $ss1)            $dd = 0;     //明日
0612:                             else if ($ss2 == $ss1)      $dd = (-1);       //今日
0613:                             else                        $dd = (-2);       //昨日
0614:                             $d2 = $arr[4] / 6;       //6時間毎
0615:                             //v.5.02
0616:                             if ($dd == (-2)) $d2 = $d2 - 4 + 1;
0617:                             //年月日代入(天気予報の場合)
0618:                             if ($forecast == 0) {
0619:                                 $dt = new DateTime($arr[1] . '-' . $arr[2] . '-' . $arr[3]);
0620:                                 for ($j = 0; $j < 3; $j++) {
0621:                                     $this->jmaWeeklyWeather[$j]['year']  = (int)date_format($dt, 'Y');
0622:                                     $this->jmaWeeklyWeather[$j]['month'] = (int)date_format($dt, 'n');
0623:                                     $this->jmaWeeklyWeather[$j]['day']   = (int)date_format($dt, 'j');
0624:                                     $this->jmaWeeklyWeather[$j]['week']  = (string)$week_name[date_format($dt, 'w')];
0625:                                     date_add($dtdate_interval_create_from_date_string('1 days'));
0626:                                 }
0627:                             }
0628:                         }
0629: 
0630:                     } else if (preg_match($pat11$TimeDefine->DateTime$arr) > 0) {
0631:                         //最初の情報は今日か明日か
0632:                         $i = (int)$TimeDefine['timeId'];
0633:                         if ($i == 1) {
0634:                         //  $dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
0635:                             //v.5.02
0636:                             $ss2 = date('Y-m-d');
0637:                             if ($ss2 < $arr[0])         $dd = 0;     //明日
0638:                             else if ($ss2 == $arr[0])   $dd = (-1);       //今日
0639:                             else                        $dd = (-2);       //昨日
0640: 
0641:                         }
0642:                     }
0643:                 }
0644:                 //地方コードは上何桁で判定するか
0645:                 $flag = FALSE;
0646:                 $n = 5;
0647:                 for ($n = 5; $n >= 1; $n--) {
0648:                     foreach ($TimeSeriesInfo->Item as $item) {
0649:                         if (substr((string)$item->Area->Code, 0, $n) != substr($area, 0, $n))      continue;
0650:                         $flag = TRUE;
0651:                     }
0652:                     if ($flag)  break;
0653:                 }
0654:                 //天気予報
0655:                 foreach ($TimeSeriesInfo->Item as $item) {
0656:                     foreach ($item->Kind as $kind) {
0657:                         if (substr((string)$item->Area->Code, 0, $n) != substr($area, 0, $n))      continue;
0658:                         if ((string)$kind->Property->Type == '天気') {
0659:                             $node = $kind->Property->WeatherPart->children(JMX_EB);
0660:                             foreach ($node->Weather as $Weather) {
0661:                                 $id = (int)$Weather->attributes()->refID + $dd;
0662:                                 if ($id < 0)    continue;    //v.5.02
0663:                                 $this->jmaWeeklyWeather[$id]['weather'] = $this->jmaShortWeather((string)$Weather);
0664:                             }
0665:                             $node = $kind->Property->WeatherCodePart->children(JMX_EB);
0666:                             foreach ($node->WeatherCode as $WeatherCode) {
0667:                                 $id = (int)$WeatherCode->attributes()->refID + $dd;
0668:                                 if ($id < 0)    continue;    //v.5.02
0669:                                 if (($id == 0) && (date('H') >= 18)) {
0670:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jma_telop2url((int)$WeatherCode, 1);
0671:                                 } else {
0672:                                     $this->jmaWeeklyWeather[$id]['image'] = $this->jma_telop2url((int)$WeatherCode, 0);
0673:                                 }
0674:                             }
0675:                         //降水確率
0676:                         } else if ((string)$kind->Property->Type == '降水確率') {
0677:                             $node = $kind->Property->ProbabilityOfPrecipitationPart->children(JMX_EB);
0678:                             foreach ($node->ProbabilityOfPrecipitation as $rainy) {
0679:                                 $id = (int)$rainy->attributes()->refID + $dd + $d2;
0680:                                 if ($id < 0)    continue;    //v.5.02
0681:                                 $rain_table[$id] = (string)$rainy;
0682:                             }
0683:                         }
0684:                     }
0685:                 }
0686:             }
0687:         } else if ((string)$info['type'] == '地点予報') {
0688:             foreach ($info->TimeSeriesInfo as $TimeSeriesInfo) {
0689:                 //日時
0690:                 foreach ($TimeSeriesInfo->TimeDefines->TimeDefine as $TimeDefine) {
0691:                     if (preg_match($pat11$TimeDefine->DateTime$arr) > 0) {
0692:                         //最初の情報は何時か
0693:                         $i = (int)$TimeDefine['timeId'];
0694:                         if ($i == 1) {
0695:                         //  $dd = ((date('j') == (int)$arr[3])) ? (-1) : 0;
0696:                             //v.5.02
0697:                             $ss2 = date('Y-m-d');
0698:                             if ($ss2 < $arr[0])         $dd = 0;     //明日
0699:                             else if ($ss2 == $arr[0])   $dd = (-1);       //今日
0700:                             else                        $dd = (-2);       //昨日
0701:                             $temp_table[$i] = 0;
0702:                             $day0 = (int)$arr[3];
0703:                         } else {
0704:                             $temp_table[$i] = ($day0 == (int)$arr[3]) ? 0 : 1;
0705:                             $day0 = (int)$arr[3];
0706:                         }
0707:                     }
0708:                 }
0709:                 //最低気温・最高気温
0710:                 $id = 1 + $dd;
0711:                 foreach ($TimeSeriesInfo->Item as $item) {
0712:                     if ((int)$item->Station->Code != $stationcontinue;
0713:                     foreach ($item->Kind->Property as $property) {
0714:                         if (preg_match($pat31, (string)$property->Type) > 0) {
0715:                             $node = $property->TemperaturePart->children(JMX_EB);
0716:                             $id += $temp_table[(int)$node->Temperature->attributes()->refID];
0717:                             if ($id < 0)    continue;    //v.5.02
0718:                             $this->jmaWeeklyWeather[$id]['temp_min'] = (string)$node->Temperature;      //v.5.02
0719:                         } else if (preg_match($pat32, (string)$property->Type) > 0) {
0720:                             $node = $property->TemperaturePart->children(JMX_EB);
0721:                             $id += $temp_table[(int)$node->Temperature->attributes()->refID];
0722:                             if ($id < 0)    continue;    //v.5.02
0723:                             $this->jmaWeeklyWeather[$id]['temp_max'] = (string)$node->Temperature;      //v.5.02
0724:                         }
0725:                     }
0726:                 }
0727:             }
0728:         }
0729:     }
0730:     //降水確率を天気予報情報へ
0731:     foreach ($rain_table as $key=>$val) {
0732:         //v.5.02
0733:         if (($dd == (-2)) && ($key >= 4))    break;
0734:         $id = floor($key / 4);
0735:         if (! isset($this->jmaWeeklyWeather[$id]['rainy'])) {
0736:             $this->jmaWeeklyWeather[$id]['rainy'] = '';
0737:         }
0738:         //週間予報で取得した降水確率があれば上書き
0739:         if (isset($this->jmaWeeklyWeather[$id]['rainy']) && is_numeric($this->jmaWeeklyWeather[$id]['rainy'])) {
0740:             $this->jmaWeeklyWeather[$id]['rainy'] = '';
0741:         }
0742:         $this->jmaWeeklyWeather[$id]['rainy'] .= (string)$val;
0743:         if ($key % 4 != 3)   $this->jmaWeeklyWeather[$id]['rainy'] .= '/';
0744:     }
0745:     //不明要素を空文字で埋める(VPFD51の場合)
0746:     if ($forecast == 0) {
0747:         for ($i = 0; $i < 3; $i++) {
0748:             if (! isset($this->jmaWeeklyWeather[$i]['weather'])) {
0749:                 $this->jmaWeeklyWeather[$i]['weather'] = '';
0750:             }
0751:             if (! isset($this->jmaWeeklyWeather[$i]['image'])) {
0752:                 $this->jmaWeeklyWeather[$i]['image'] = '';
0753:             }
0754:             if (! isset($this->jmaWeeklyWeather[$i]['rainy'])) {
0755:                 $this->jmaWeeklyWeather[$i]['rainy'] = '';
0756:             }
0757:             if (! isset($this->jmaWeeklyWeather[$i]['temp_min'])) {
0758:                 $this->jmaWeeklyWeather[$i]['temp_min'] = '';
0759:             }
0760:             if (! isset($this->jmaWeeklyWeather[$i]['temp_max'])) {
0761:                 $this->jmaWeeklyWeather[$i]['temp_max'] = '';
0762:             }
0763:         }
0764:     }
0765: 
0766:     //配列へ代入
0767:     $items = $this->jmaWeeklyWeather;
0768:     return TRUE;
0769: }

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 に代入するよう工夫した。
さらに、情報取得の時間帯によっては予報情報が入っていない場合があるので、最後に不明要素を空文字で埋めるようにした。

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

0476: /**
0477:  * 週間天気予報表を作成する
0478:  * @param array $items    週間天気予報情報を格納した配列
0479:  * @param int   $start    作成開始オフセット(0~6)
0480:  * @param int   $goal     作成終了オフセット(0~6)
0481:  * @param int   $columns  天気予報の表示列数(初期値:COLUMNS)
0482:  * @param int   $forecast 0:天気予報,1:週間天気予報(省略時:1)
0483:  * @return int 配列に格納した情報件数/0=失敗
0484: */
0485: function makeWeeklyWeather($items$start$goal$columns=COLUMNS$forecast=1) {
0486:     //引数のベリファイ
0487:     if ($start   < 0 || $start   > 6)   return FALSE;
0488:     if ($goal    < 0 || $goal    > 6)   return FALSE;
0489:     if ($columns < 0 || $columns > 7)   return FALSE;
0490:     if ($start > $goal)                 return FALSE;
0491:     if ($forecast == 0) {
0492:         if ($start   > 2)   return FALSE;
0493:         if ($goal    > 2)   return FALSE;
0494:         if ($columns > 3)   return FALSE;
0495:     }
0496: 
0497: $outstr =<<< EOT
0498: <table class="weather">
0499: <caption>{$items['stationName']}の天気予報</caption>
0500: 
0501: EOT;
0502: 
0503:     $i = $start;
0504:     while ($i <= $goal) {
0505:         //日付の行
0506:         $j = 0;
0507:         while ($j < $columns) {
0508:             if ($j == 0)    $outstr .= "<tr>\n";
0509:             if (($i <= $goal) && isset($items[$i]['week'])) {
0510:                 $mmddww = sprintf("%02d/%02d<span class=\"wsmall\">(%s)</span>", $items[$i]['month'], $items[$i]['day'], $items[$i]['week']);
0511: $outstr .=<<< EOT
0512: <td class="dt">{$mmddww}</td>
0513: 
0514: EOT;
0515:             } else {
0516:                 $outstr .= "<td class=\"dt\">&nbsp;</td>\n";
0517:             }
0518:             $i++;
0519:             $j++;
0520:             if ($j == $columns$outstr .= "</tr>\n";
0521:         }
0522:         //天気予報の行
0523:         $i -= $columns;
0524:         $j = 0;
0525:         while ($j < $columns) {
0526:             //新しい行
0527:             if ($j == 0)    $outstr .= "<tr>\n";
0528:             if ($i <= $goal) {
0529:                 $mmddww = sprintf("%02d/%02d<span class=\"wsmall\">(%s)</span>", $items[$i]['month'], $items[$i]['day'], $items[$i]['week']);
0530:                 $temp_max = ((string)$items[$i]['temp_max'] != '') ? $items[$i]['temp_max'] . '' : '-';
0531:                 $temp_min = ((string)$items[$i]['temp_min'] != '') ? $items[$i]['temp_min'] . '' : '-';
0532: $outstr .=<<< EOT
0533: <td class="info">
0534: {$items[$i]['weather']}<br />
0535: <img class="wicon" src="{$items[$i]['image']}" /><br />
0536: <span class="wsmall">
0537: {$items[$i]['rainy']}%<br />
0538: {$temp_max}/{$temp_min}
0539: </span>
0540: </td>
0541: 
0542: EOT;
0543:             //情報なし列
0544:             } else {
0545:                 $outstr .= "<td class=\"info\">&nbsp;</td>\n";
0546:             }
0547:             $i++;
0548:             $j++;
0549:             if ($j == $columns$outstr .= "</tr>\n";
0550:         }
0551:     }
0552:     $outstr .= "</table>\n";
0553: 
0554:     return $outstr;
0555: }

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

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

0647: //パラメータ
0648: $id       = (int)getParam('id',  FALSE, 0);
0649: $region   = (string)sprintf('%02d', (int)getParam('region',  FALSE, 0));
0650: $pref     = (string)sprintf('%02d', (int)getParam('pref',    FALSE, 0));
0651: $station  = (string)sprintf('%05d', (int)getParam('station', FALSE, 0));
0652: $outenc   = (string)getParam('charset',  FALSEINTERNAL_ENCODING);
0653: $start    = (int)getParam('start',    FALSEDEF_START);
0654: $goal     = (int)getParam('goal',     FALSEDEF_GOAL);
0655: $forecast = (int)getParam('forecast', FALSEDEF_FORECAST);
0656: $columns  = COLUMNS;

URL パラメータを使って

jmaWeeklyWeather.php?id=1&forecast=1&station=49142


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

0240: <style>
0241: /* エラー表示 */
0242: p.werror {
0243:     color: red;
0244: }
0245: /* 天気予報表 */
0246: table.weather {
0247:     width: {$width}px;
0248:     border:solid 1px #000000;
0249:     border-collapse:collapse;
0250:     margin-top:10px;
0251: 
0252: }
0253: /* 天気予報表:月日表示部 */
0254: table.weather td.dt {
0255:     width: {$width}px;
0256:     border:solid 1px #000000;
0257:     border-collapse: collapse;
0258:     padding:4px;
0259:     white-space:nowrap;
0260:     text-align:center;
0261: }
0262: /* 天気予報表:予報表示部 */
0263: table.weather td.info {
0264:     width: {$width}px;
0265:     border:solid 1px #000000;
0266:     border-collapse: collapse;
0267:     padding:4px;
0268:     white-space:nowrap;
0269:     text-align:center;
0270: }
0271: /* 天気予報表:予報アイコン */
0272: img.wicon {
0273:     width: 60px;
0274: }
0275: /* 天気予報表:小さい文字 */
0276: span.wsmall {
0277:     font-size: small;
0278: }
0279: </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 を使って生成することができる。
予報地点の緯度・経度は、気象庁の「地域気象観測システム(アメダス)」ページにある地域気象観測所一覧(CSV 形式)を参照する。解凍してできる CSV ファイル名を、定数 FILE_AMEDAS にセットすること。
予報地点情報ファイル(xml) jmaweatherspots date 作成日時 version バージョン spot code 電文コード VPFW50|VPFD51 page ページ番号 regionCode 地方コード regionName 地方名 prefCode 都道府県コード prefName 都道府県名 areaCode 予報地方コード areaName 予報地方名 stationCode 予報地点コード stationName 予報地点名 location 場所 latitude 緯度(世界測地系) longitude 経度(世界測地系)

0462: /**
0463:  * 気象庁防災情報XMLから各地点の最新の府県週間天気予報情報URLを取得
0464:  * @param object $pwt    pahooWeatherオブジェクト
0465:  * @param string $code   データコード
0466:  * @param array  $items  府県週間天気予報情報URLを格納する配列
0467:  *                  [page]['url']  URL
0468:  *                  [page]['dt']   日時
0469:  * @param string $errmsg エラーメッセージを格納
0470:  * @return int 格納したURLの数/FALSE:エラー発生
0471: */
0472: function getWeatherUrls($pwt$code, &$items, &$errmsg) {
0473:     //マッチするURLパターン
0474:     $pat1 = sprintf('/http\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)%s\_([0-9]+)\.xml/ui', $code);
0475: 
0476:     //気象庁防災情報XML:長期フィード - 定時配信
0477:     $errmsg = '';
0478:     unknown_certificate();
0479:     $xml = @simplexml_load_file($pwt::FEED_REGULAR_L);
0480:     //レスポンス・チェック
0481:     if (! isset($xml->entry)) {
0482:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0483:         return FALSE;
0484:     }
0485: 
0486:     //フィードを解析
0487:     $items = array();
0488:     $cnt = 0;
0489:     foreach ($xml->entry as $entry) {
0490:         if (preg_match($pat1, (string)$entry->id$arr) > 0) {
0491:             $dt   = (string)$arr[1];
0492:             $page = (string)$arr[2];
0493:             //登録済み
0494:             if (isset($items[$page])) {
0495:                 //より新しければ要素を上書き
0496:                 if ($dt > $items[$page]['dt']) {
0497:                     $items[$page]['dt']  = (string)$dt;
0498:                     $items[$page]['url'] = (string)$entry->id;
0499:                 }
0500:             //未登録
0501:             } else {
0502:                 $items[$page]['dt']  = (string)$dt;
0503:                 $items[$page]['url'] = (string)$entry->id;
0504:                 $cnt++;
0505:             }
0506:         }
0507:     }
0508:     //ソート
0509:     ksort($items);
0510: 
0511:     return $cnt;
0512: }

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

0514: /**
0515:  * 1つの府県週間天気予報情報から予報地点情報を取得
0516:  * @param string $url    府県週間天気予報情報URL
0517:  * @param array  $items  予報地点情報を格納する配列
0518:  * @param string $errmsg エラーメッセージを格納
0519:  * @return int 格納した予報地点の数/FALSE:エラー発生
0520: */
0521: function getWeatherSpotsSub($url, &$items, &$errmsg) {
0522:     $errmsg = '';
0523:     unknown_certificate();
0524:     $xml = @simplexml_load_file($url);
0525:     //レスポンス・チェック
0526:     if (! isset($xml->Body->MeteorologicalInfos)) {
0527:         $errmsg = '府県週間天気予報情報を取得できません';
0528:         return FALSE;
0529:     }
0530: 
0531:     $flag1 = $flag2 = FALSE;
0532:     foreach ($xml->Body->MeteorologicalInfos as $MeteorologicalInfos) {
0533:         //地方名
0534:         if (!$flag1 && $MeteorologicalInfos['type'] == '区域予報') {
0535:             $flag1 = TRUE;
0536:             $cnt = 0;
0537:             foreach ($MeteorologicalInfos->TimeSeriesInfo->Item as $item) {
0538:                 $items[$cnt]['areaName'] = (string)$item->Area->Name;
0539:                 $items[$cnt]['areaCode'] = (string)$item->Area->Code;
0540:                 $cnt++;
0541:             }
0542:         //予報地点名
0543:         } else if (!$flag2 && $MeteorologicalInfos['type'] == '地点予報') {
0544:             $flag2 = TRUE;
0545:             $cnt = 0;
0546:             foreach ($MeteorologicalInfos->TimeSeriesInfo->Item as $item) {
0547:                 $items[$cnt]['stationName'] = (string)$item->Station->Name;
0548:                 $items[$cnt]['stationCode'] = (string)$item->Station->Code;
0549:                 $cnt++;
0550:             }
0551:         }
0552:     }
0553:     //余分な予報地点を削除
0554: /**
0555:     foreach ($items as $key=>$item) {
0556:         if ($key >= $cnt) {
0557:             unset($items[$key]['stationName']);
0558:             unset($items[$key]['stationCode']);
0559:         }
0560:     }
0561: **/
0562: 
0563:     return $cnt;
0564: }

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

0620: /**
0621:  * 予報地点情報に場所・緯度・経度を代入:VPFD51用
0622:  * @param array  $amedas 地域観測所一覧
0623:  * @param array  $spot   予報地点情報
0624:  * @return なし
0625: */
0626: function addLocation($amedas, &$spot) {
0627:     //地域観測所を探索
0628:     foreach ($amedas as $item) {
0629:         if ($spot['stationCode'] == $item['stationCode']) {
0630:             $spot['location']  = $item['location'];
0631:             $spot['latitude']  = $item['latitude'];
0632:             $spot['longitude'] = $item['longitude'];
0633:             return;
0634:         }
0635:     }
0636: }

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

0600: /**
0601:  * 予報地点情報に地方名を代入:VPFD51用
0602:  * @param array  $spot  予報地点情報
0603:  * @return なし
0604: */
0605: function addAreaName(&$spot) {
0606:     global $TableStation;
0607: 
0608:     foreach ($TableStation as $pref=>$arr1) {
0609:         foreach ($arr1 as $areaName=>$arr2) {
0610:             foreach ($arr2 as $stationName) {
0611:                 if (($spot['prefName'] == $pref) && ($spot['stationName'] == $stationName)) {
0612:                     $spot['areaName'] = (string)$areaName;
0613:                     return;
0614:                 }
0615:             }
0616:         }
0617:     }
0618: }

なお、天気予報(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
  • データ構造を変更したため、差し替えること。

活用例

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

質疑応答

【質問】

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