PHPで台風情報を取得する

(1/1)
今年(2013 年)は台風の被害が多い年となった。
そこで、気象庁の台風情報のサイトから、現在日本に接近している台風の情報を取得する PHP スクリプトを作成してみることにする。

2021 年(令和 3 年)2 月 24 日の気象庁サイト・リニューアルにより、スクレイピングによる取り出しが難しくなったため、気象庁防災情報 XMLからの情報取得に変更した。あわせてキャッシュ・システムを導入した。

(2021 年 5 月 13 日)熱帯低気圧で予報円だけデータがある場合は除外
(2021 年 5 月 2 日)台風情報が存在しないときの表示不具合を改善した。
(2021 年 4 月 25 日)熱帯低気圧は表示しないようにした。
(2021 年 4 月 19 日)気象庁防災情報 XML からの取得に変更し、キャッシュ・システムを導入。

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

PHPで台風情報を取得する

目次

サンプル・プログラム

圧縮ファイルの内容
typhoon.phpサンプル・プログラム本体
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。

準備:pahooGeoCode クラス

0037: class pahooGeoCode {
0038:     var $items;      //検索結果格納用
0039:     var $error;      //エラーフラグ
0040:     var $hits;       //検索ヒット件数
0041:     var $webapi; //直前に呼び出したWebAPI URL
0042: 
0043:     //Google Cloud Platform APIキー
0044:     //https://cloud.google.com/maps-platform/
0045:     //※Google Maps APIを利用しないのなら登録不要
0046:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
0047:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
0048: 
0049:     //Yahoo! JAPAN Webサービス アプリケーションID
0050:     //https://e.developer.yahoo.co.jp/register
0051:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
0052:     var $YAHOO_APPLICATION_ID = '*****************************';

地図サービスを利用するために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHP でクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図や住所検索として Google を利用するのであれば、Google Cloud Platform API キー が必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を参照されたい。

準備:地図サービスの選択

0034: //地図描画サービスの選択
0035: //    0:Google
0036: //    2:地理院地図・OSM
0037: define('MAPSERVICE', 2);

表示する地図は、Google マップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVICE に値を設定すること。
PHPで台風情報を取得する
地理院地図表示
PHPで台風情報を取得する
オープンストリートマップ表示

準備:キャッシュ・システム

0060: //キャッシュ保持時間(分) 0:キャッシュしない
0061: //気象庁へのアクセス負荷軽減+台風の進路プロット
0062: define('LIFE_CACHE_FEED',   5);            //高頻度 - 随時フィードに対して
0063: define('LIFE_CACHE_FEED_L', 120);        //長期   - 随時フィードに対して
0064: define('LIFE_CACHE_DATA', (60 * 24 * 14)); //台風情報の保持時間(進路プロット)
0065: 
0066: //キャッシュ・ディレクトリ
0067: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
0068: define('DIR_CACHE_FEED',   './pcache_typhoon1/');
0069: define('DIR_CACHE_FEED_L', './pcache_typhoon2/');
0070: define('DIR_CACHE_DATA',   './pcache_typhoon3/');

気象庁サイトへ負荷を掛けないこと、また、過去の台風の進路をプロットすることを目的として、キャッシュ・クラス pahooCache を導入した。使用方法については、「PHP で天気予報を求める - キャッシュ・システム」を参照いただきたい。
キャッシュ保持時間、キャッシュ・ディレクトリともに、自サイトの環境に応じて変更してほしい。
LIFE_CACHE_DATA は、過去の台風の進路をプロットできるように、初期値では 14 日間を指定してある。

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

気象庁防災情報 XML フォーマットについては、「PHP で天気予報を求める - 気象庁防災情報 XML フォーマット」をご覧いただきたい。
今回は、高頻度 - 随時フィード および 長期 - 随時フィード にアクセスし、電文コード VPTW60 の台風解析・予報情報(5日予報)を取得する。

VPTW60の構造

台風解析・予報情報(5 日予報)XML VPTW60 には、その時点の台風情報と、5 日先の予報円(予報進路)が収められており、構造は下記の通りである。ここから必要な情報を取り出す。
注意すべきは、1 つの XML ファイルの中に 1 つまたは複数の台風情報が含まれていること。
また、過去の中心位置は分からないので、キャッシュ・ファイルとして保存した VZSA50 から中心位置を拾い出し、それを台風の過去進路にする方針である。
VPTW60の構造(xml) Report Control Title 台風解析・予報情報(5日予報)(H30) DateTime 配信日時 Status ステータス EditorialOffice 編集者 PublishingOffice 配信者 Head Title タイトル ReportDateTime 配信日時 TargetDateTime 予報対象日時 TargetDuration 予報間隔 InfoType 発表 InfoKind 情報の種類 Serial 通し番号 InfoKindVersion 情報バージョン Body MeteorologicalInfos MeteorologicalInfo DateTime 予報日時 Item Kind Property Type 呼称 TyphoonNamePart Name 名称(英語) NameKana 名称(日本語) Number 番号(西暦下2桁+連番2桁) Remark Kind Property Type 階級 ClassPart Kind Property Type 中心 CenterPart Location 位置(テキスト) Kind Property Type WindPart WarningAreaPart WarningAreaPart MeteorologicalInfo DateTime 24時間後予報日時 Item Kind Property Type 階級 ClassPart Kind Property Type 中心 CenterPart ProbabilityCircle Location 位置(テキスト) Kind Property Type WindPart WarningAreaPart WarningAreaPart MeteorologicalInfo DateTime 48時間後予報日時 MeteorologicalInfo DateTime 72時間後予報日時 MeteorologicalInfo DateTime 96時間後予報日時 MeteorologicalInfo DateTime 120時間後予報日時

準備:マップの表示サイズなど

0042: //マップの表示サイズ(単位:ピクセル)
0043: define('MAP_WIDTH',  600);
0044: define('MAP_HEIGHT', 480);
0045: //マップID
0046: define('MAPID', 'map_id');
0047: //マップ座標
0048: define('DEF_LATITUDE',  35.0);           //緯度
0049: define('DEF_LONGITUDE', 137.0);           //経度
0050: define('DEF_TYPE',     'GSISTD');        //マップタイプ
0051: define('DEF_ZOOM',     5);              //ズーム
0052: //マップ描画色
0053: define('COLOR_NAME1',    '#FF8800');    //台風名称の描画色
0054: define('COLOR_NAME2',    '#0000FF');    //熱低名称の描画色
0055: define('COLOR_LINE',     '#0000FF');   //過去経路の描画色
0056: define('COLOR_WIND1',    '#FFFF00');    //強風域の描画色
0057: define('COLOR_WIND2',    '#FF0000');    //暴風域の描画色
0058: define('COLOR_FORECAST', '#FFFFFF');   //予報円の描画色

マップの表示サイズなどの初期値は、とくに断りがないかぎりは自由に変更できる。

解説:台風に関する情報URLを取得

0214: /**
0215:  * 気象庁防災情報XMLから台風に関する情報URLを取得
0216:  * @param array  $urls    URL格納配列
0217:  * @param string $errmsg  エラーメッセージ格納用
0218:  * @return bool TRUE:取得成功/FALSE:取得失敗
0219: */
0220: function jmaGetTyphoonURLs(&$urls, &$errmsg) {
0221:     //URLパターン
0222:     $vptw = '/http\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VPTW6[0-9]+\_[0-9]+\.xml/ui';
0223: 
0224:     //随時フィードの解析
0225:     $cnt = 0;
0226:     $pcc = new pahooCache(LIFE_CACHE_FEEDDIR_CACHE_FEED);
0227:     $xml = $pcc->simplexml_load(FEED);
0228:     //レスポンス・チェック
0229:     if ($pcc->iserror() || !isset($xml->entry)) {
0230:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0231:         return FALSE;
0232:     }
0233:     foreach ($xml->entry as $node) {
0234:         //URLを取得
0235:         if (preg_match($vptw$node->id$arr) > 0) {
0236:             $urls[$cnt] = $arr[0];
0237:             $cnt++;
0238:         }
0239:     }
0240:     $pcc = NULL;
0241: 
0242:     //長期フィードの解析
0243:     $pcc = new pahooCache(LIFE_CACHE_FEED_LDIR_CACHE_FEED_L);
0244:     $xml = $pcc->simplexml_load(FEED_L);
0245:     //レスポンス・チェック
0246:     if ($pcc->iserror() || !isset($xml->entry)) {
0247:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0248:         return FALSE;
0249:     }
0250:     foreach ($xml->entry as $node) {
0251:         //URLを取得
0252:         if (preg_match($vptw$node->id$arr) > 0) {
0253:             if (array_search($arr[0]$urls) === FALSE) {
0254:                 $urls[$cnt] = $arr[0];
0255:                 $cnt++;
0256:             }
0257:         }
0258:     }
0259:     $pcc = NULL;
0260: 
0261:     //エラー・チェック
0262:     if ($cnt == 0) {
0263:         $errmsg = '直近の台風情報はありません';
0264:         return FALSE;
0265:     }
0266: 
0267:     //URLを日時の新しい順にソート
0268:     rsort($urls);
0269: 
0270:     return TRUE;
0271: }

フィードから台風に関する情報 URL を取得するユーザー関数は jmaGetTyphoonURLs である。
正規表現で VPTW60 を含む URL を取り出し配列に格納し、日時の新しい順に並べ替えておく。

解説:台風情報を読み込む

0273: /**
0274:  * 台風報取得(気象庁防災情報XMLから)
0275:  * @param object $pgc     pahooGeoCodeオブジェクト
0276:  * @param array  $items   台風情報を格納する配列
0277:  * @param string $urls    情報XMLのURLを格納する配列
0278:  * @param string $errmsg  エラーメッセージ格納用
0279:  * @return bool TRUE:取得成功/FALSE:失敗
0280: */
0281: function getTyphoon($pgc, &$items, &$urls, &$errmsg) {
0282:     //名前空間
0283:     define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
0284:     //マッチングパターン
0285:     $pat2 = '/([\+\-][0-9]{1,2}\.[0-9]+)([\+\-][0-9]{1,3}\.[0-9]+)/ui'; //緯度・経度
0286:     $pat3 = '/予報[  ]*([01234567890-9]+)時間後/ui';   //予報
0287: 
0288:     //オブジェクト生成
0289:     $pcc = new pahooCache(LIFE_CACHE_DATADIR_CACHE_DATA);
0290: 
0291:     //最新の台風に関する情報URLを取得
0292:     jmaGetTyphoonURLs($urls$errmsg);
0293:     if ($errmsg != '')  return FALSE;
0294: 
0295:     foreach ($urls as $key=>$vptw) {
0296:         //台風情報の取得
0297:         $xml = $pcc->simplexml_load($vptw);
0298:         //レスポンス・チェック
0299:         if ($pcc->iserror() || !isset($xml->Body->MeteorologicalInfos)) {
0300:             $errmsg = '気象庁防災情報XMLから台風情報を取得できません';
0301:             return FALSE;
0302:         }
0303: 
0304:         //最新の台風情報
0305:         if ($key == 0) {
0306:             foreach ($xml->Body->MeteorologicalInfos as $infos) {
0307:                 $cnt = 0;
0308:                 foreach ($infos->MeteorologicalInfo as $info) {
0309:                     //実況
0310:                     if ($info->DateTime['type'] == '実況') {
0311:                         foreach ($info->Item->Kind as $kind) {
0312:                             if (isset($kind->Property->Type)) {
0313:                                 //呼称
0314:                                 if ($kind->Property->Type == '呼称') {
0315:                                     $num = (string)$kind->Property->TyphoonNamePart->Number;
0316:                                     if ($num == '')        break;
0317:                                     $items[$num]['Name'] = (string)$kind->Property->TyphoonNamePart->Name;
0318:                                     $items[$num]['NameKana'] = (string)$kind->Property->TyphoonNamePart->NameKana;
0319:                                     $items[$num][$cnt]['kind'] = '実況';
0320:                                     $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0321:                                     //古い台風情報かどうか
0322:                                     $tt = time() - strtotime($info->DateTime);
0323:                                     $items[$num]['Valid'] = ($tt < SCRAP_TIME) ? TRUE : FALSE;
0324:                                 //階級
0325:                                 } else if ($kind->Property->Type == '階級') {
0326:                                     $node = $kind->Property->ClassPart->children(JMX_EB);
0327:                                     $items[$num][$cnt]['TyphoonClass'] = (string)$node->TyphoonClass;
0328:                                     $items[$num][$cnt]['AreaClass'] = (string)$node->AreaClass;
0329:                                     $items[$num][$cnt]['IntensityClass'] = (string)$node->IntensityClass;
0330:                                 //中心
0331:                                 } else if ($kind->Property->Type == '中心') {
0332:                                     $node = $kind->Property->CenterPart->children(JMX_EB);
0333:                                     //中心位置
0334:                                     $items[$num][$cnt]['Location'] = (string)$kind->Property->CenterPart->Location;
0335:                                     foreach ($node->Coordinate as $val) {
0336:                                         if (preg_match($pat2$val$arr) > 0) {
0337:                                             $items[$num][$cnt]['latitude'] = (float)$arr[1];
0338:                                             $items[$num][$cnt]['longitude'] = (float)$arr[2];
0339:                                         }
0340:                                     }
0341:                                     //移動速度
0342:                                     $items[$num][$cnt]['Direction'] = (string)$node->Direction;
0343:                                     foreach ($node->Speed as $val) {
0344:                                         if (isset($val->attributes()['condition'])) {
0345:                                             $items[$num][$cnt]['Speed'] = (string)$val->attributes()['condition'];
0346:                                         } else if ($val->attributes()['unit'] == 'km/h') {
0347:                                             $items[$num][$cnt]['Speed'] = (string)$val;
0348:                                         }
0349:                                     }
0350:                                     //中心気圧
0351:                                     $items[$num][$cnt]['Pressure'] = (int)$node->Pressure;
0352:                                 //風
0353:                                 } else if ($kind->Property->Type == '') {
0354:                                     $node = $kind->Property->WindPart->children(JMX_EB);
0355:                                     foreach ($node->WindSpeed as $val) {
0356:                                         if (($val->attributes()['condition'] == '中心付近') && ($val->attributes()['unit'] == 'm/s')) {
0357:                                             $items[$num][$cnt]['centerWindSpeed'] = (int)$val;
0358:                                         } else if (($val->attributes()['type'] == '最大瞬間風速') && ($val->attributes()['unit'] == 'm/s')) {
0359:                                             $items[$num][$cnt]['maxWindSpeed'] = (int)$val;
0360:                                         }
0361:                                     }
0362:                                     //暴風域・強風域
0363:                                     foreach ($kind->Property->WarningAreaPart as $val) {
0364:                                         $key = (string)$val['type'];
0365:                                         //半径
0366:                                         $node = $val->children(JMX_EB);
0367:                                         $n = 0;
0368:                                         foreach ($node->Circle->Axes->Axis as $axis) {
0369:                                             if (isset($axis->Direction) && ($axis->Direction != '')) {
0370:                                                 $items[$num][$cnt][$key][$n]['direction'] = (string)$axis->Direction;
0371:                                             } else {
0372:                                                 $items[$num][$cnt][$key][$n]['direction'] = (string)$axis->Direction->attributes()['condition'];
0373:                                             }
0374:                                             foreach ($axis->Radius as $val) {
0375:                                                 if ($val->attributes()['unit'] == 'km') {
0376:                                                     $items[$num][$cnt][$key][$n]['radius'] = (int)$val;
0377:                                                 }
0378:                                             }
0379:                                             $n++;
0380:                                         }
0381:                                     }
0382:                                 }
0383:                             }
0384:                         }
0385:                     //予報
0386:                     } else if (preg_match($pat3, (string)$info->DateTime['type'], $arr) > 0) {
0387:                         if ($num == '')        break;       //ver.2.04
0388:                         $cnt++;
0389:                         $items[$num][$cnt]['kind'] = '予報';
0390:                         $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0391:                         foreach ($info->Item->Kind as $kind) {
0392:                             //階級
0393:                             if ($kind->Property->Type == '階級') {
0394:                                 $node = $kind->Property->ClassPart->children(JMX_EB);
0395:                                 $items[$num][$cnt]['TyphoonClass'] = (string)$node->TyphoonClass;
0396:                                 $items[$num][$cnt]['AreaClass'] = isset($node->AreaClass) ? (string)$node->AreaClass : '';
0397:                                 $items[$num][$cnt]['IntensityClass'] = isset($node->IntensityClass) ? (string)$node->IntensityClass : '';
0398:                             //中心位置(予報円)
0399:                             } else if ($kind->Property->Type == '中心') {
0400:                                 $node = $kind->Property->CenterPart->ProbabilityCircle->children(JMX_EB);
0401:                                 foreach ($node->BasePoint as $val) {
0402:                                     if (preg_match($pat2$val$arr) > 0) {
0403:                                         $items[$num][$cnt]['latitude'] = (float)$arr[1];
0404:                                         $items[$num][$cnt]['longitude'] = (float)$arr[2];
0405:                                     }
0406:                                 }
0407:                                 //半径
0408:                                 $n = 0;
0409:                                 foreach ($node->Axes->Axis as $axis) {
0410:                                     if (isset($axis->Direction) && ($axis->Direction != '')) {
0411:                                         $items[$num][$cnt]['予報円'][$n]['direction'] = (string)$axis->Direction;
0412:                                     } else {
0413:                                         $items[$num][$cnt]['予報円'][$n]['direction'] = (string)$axis->Direction->attributes()['condition'];
0414:                                     }
0415:                                     foreach ($axis->Radius as $val) {
0416:                                         if ($val->attributes()['unit'] == 'km') {
0417:                                             $items[$num][$cnt]['予報円'][$n]['radius'] = (int)$val;
0418:                                         }
0419:                                     }
0420:                                     $n++;
0421:                                 }
0422:                             //風
0423:                             } else if ($kind->Property->Type == '') {
0424:                                 $node = $kind->Property->WindPart->children(JMX_EB);
0425:                                 foreach ($node->WindSpeed as $val) {
0426:                                     if (($val->attributes()['condition'] == '中心付近') && ($val->attributes()['unit'] == 'm/s')) {
0427:                                         $items[$num][$cnt]['centerWindSpeed'] = (int)$val;
0428:                                     } else if (($val->attributes()['type'] == '最大瞬間風速') && ($val->attributes()['unit'] == 'm/s')) {
0429:                                         $items[$num][$cnt]['maxWindSpeed'] = (int)$val;
0430:                                     }
0431:                                 }
0432:                             }
0433:                         }
0434:                     }
0435:                 }
0436:             }
0437:         //過去の台風情報
0438:         } else {
0439:             foreach ($xml->Body->MeteorologicalInfos as $infos) {
0440:                 foreach ($infos->MeteorologicalInfo as $info) {
0441:                     //過去の位置
0442:                     if ($info->DateTime['type'] == '実況') {
0443:                         foreach ($info->Item->Kind as $kind) {
0444:                             if (isset($kind->Property->Type)) {
0445:                                 //呼称
0446:                                 if ($kind->Property->Type == '呼称') {
0447:                                     $num = (string)$kind->Property->TyphoonNamePart->Number;
0448:                                     if ($num == '')        break;
0449:                                     $cnt = 0;
0450:                                     while (isset($items[$num][$cnt]))   $cnt++;
0451:                                     $items[$num][$cnt]['kind'] = '過去';
0452:                                     $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0453:                                 //中心
0454:                                 } else if ($kind->Property->Type == '中心') {
0455:                                     $node = $kind->Property->CenterPart->children(JMX_EB);
0456:                                     //中心位置
0457:                                     foreach ($node->Coordinate as $val) {
0458:                                         if (preg_match($pat2$val$arr) > 0) {
0459:                                             $items[$num][$cnt]['latitude'] = (float)$arr[1];
0460:                                             $items[$num][$cnt]['longitude'] = (float)$arr[2];
0461:                                         }
0462:                                     }
0463:                                 }
0464:                             }
0465:                         }
0466:                     }
0467:                 }
0468:             }
0469:         }
0470:     }
0471:     //オブジェクト解放
0472:     $pcc = NULL;
0473: 
0474:     return TRUE;
0475: }

VPTW60 から台風情報をを配列 $items へ格納するユーザー関数が getTyphoon である。

XML ファイルを読み込んだら、まず、実況情報か予報情報かを識別する。
予報情報の場合、番号を見て、まだ登録されていない情報であれば、最新の実況情報として配列に代入する。登録済みの情報であれば、過去の台風情報として配列に代入する。過去の情報は、台風の過去の進路としてマッピングするときに参照する。
なお、現在日時から実況日時を減じ、定数 SCRAP_TIME を超えていたら、古い台風情報として、配列には記録するものの、要素 Valid に FALSE を代入し、古い台風情報であることを明示する。

暴風域・強風域、予報円については、半径の値が複数存在する。たとえば、強風域が北東 240km、南西200km となっていたら、台風の中心から北東へズレたところに強風域の中心がある。

解説:台風情報を描くスクリプトを生成

0522: /**
0523:  * 暴風域、強風域、予報円の描画スクリプトを生成する
0524:  * @param object $pgc   pahooGeoCodeオブジェクト
0525:  * @param array  $infos  台風情報
0526:  * @return string スクリプト/FALSE:生成失敗
0527: */
0528: function jsTyphoonMap($pgc$infos) {
0529:     $js = '';
0530:     foreach ($infos as $info) {
0531:         $key = 0;
0532:         //台風以外ならスキップ
0533:         if (preg_match('/台風/ui', $info[$key]['TyphoonClass']) == 0)    continue;
0534:         //古い台風情報ならスキップ
0535:         if ($info['Valid'] == FALSEcontinue;
0536: 
0537:         //暴風域
0538:         list($latitude$longitude$radius) = shiftCircle($pgc$info[$key]['latitude'], $info[$key]['longitude'], $info[$key]['暴風域']);
0539:         $radius *= 1000;
0540:         $js .= $pgc->jsCircle($longitude$latitude$radiusCOLOR_WIND2, '1.0', 2, MAPSERVICE);
0541:         //強風域
0542:         list($latitude$longitude$radius) = shiftCircle($pgc$info[$key]['latitude'], $info[$key]['longitude'], $info[$key]['強風域']);
0543:         $radius *= 1000;
0544:         $js .= $pgc->jsCircle($longitude$latitude$radiusCOLOR_WIND1, '1.0', 2, MAPSERVICE);
0545: 
0546:         $key = 1;
0547:         $cnt = 1;
0548:         $points[0]['longitude'] = $info[0]['longitude'];
0549:         $points[0]['latitude']  = $info[0]['latitude'];
0550:         while (isset($info[$key])) {
0551:             //予報円
0552:             if ($info[$key]['kind'] == '予報') {
0553:                 list($latitude$longitude$radius) = shiftCircle($pgc$info[$key]['latitude'], $info[$key]['longitude'], $info[$key]['予報円']);
0554:                 $radius *= 1000;
0555:                 $js .= $pgc->jsCircle($longitude$latitude$radiusCOLOR_FORECAST, '1.0', 2, MAPSERVICE);
0556:                 preg_match('/[0-9]{4}\-[0-9]{2}\-([0-9]{2})T([0-9]{2})/ui', $info[$key]['DateTime'], $arr);
0557:                 $dt = sprintf('%d日%d時', $arr[1]$arr[2]);
0558:                 list($lat$lng) = $pgc->getPointDistance($longitude$latitude, 0 - $radius - 30000, 9);
0559:                 $js .= $pgc->jsLabel($lat$lng$dt, 12, COLOR_FORECAST, 'normal', MAPSERVICE);
0560: 
0561:             //過去の位置
0562:             } else {
0563:                 $points[$cnt]['longitude'] = $info[$key]['longitude'];
0564:                 $points[$cnt]['latitude']  = $info[$key]['latitude'];
0565:                 $cnt++;
0566:             }
0567:             $key++;
0568:         }
0569:     }
0570:     //過去の移動経路
0571:     if ($js != '') {
0572:         $js .= $pgc->jsLine($pointsCOLOR_LINE, '1.0', 3, MAPSERVICE);
0573:     }
0574: 
0575:     return $js;
0576: }

ユーザー関数が jsTyphoonMap は、暴風域・強風域、予報円を含め、マップ上に台風情報を描くためのスクリプトを生成する。
前述の通り、円の中心がズレている場合があり、そのためのユーザー関数 shiftCircle を呼び出して使う。

解説:マップ描画用情報を生成

0578: /**
0579:  * 台風情報からマップ描画用情報を生成する
0580:  * @param array  $infos  台風情報
0581:  * @param array  $items  マップ描画用情報を格納
0582:  * @param string $table  HTML文(表形式)を格納
0583:  * @param int    $count  有効な台風情報の数
0584:  * @return array(日時,緯度,経度) 発表日時,予報円の最後の中心座標
0585: */
0586: function getTyphoonInfo($infos, &$items, &$table, &$count) {
0587:     //台風情報一覧
0588: $table =<<< EOD
0589: <table class="plists">
0590: <th>名称</th>
0591: <th>位置</th>
0592: <th>中心気圧</th>
0593: <th>最大瞬間風速</th>
0594: <th>進路</th>
0595: </tr>
0596: 
0597: EOD;
0598: 
0599:     $dt0 = $lat0 = $lng0 = FALSE;
0600:     $cnt = 1;
0601:     foreach ($infos as $key=>$info) {
0602:         //古い台風情報ならスキップ
0603:         if ($info['Valid'] == FALSEcontinue;
0604: 
0605:         if (preg_match('/台風/ui', $info[0]['TyphoonClass']) > 0) {
0606:             $num = sprintf('台%d', (int)substr($key, 2, 2));
0607:             $name = sprintf('台風%d号(%s)', (int)substr($key, 2, 2), $info['NameKana']);
0608:         } else {
0609:             continue;
0610:         }
0611:         $items[$cnt]['longitude'] = $info[0]['longitude'];
0612:         $items[$cnt]['latitude']  = $info[0]['latitude'];
0613:         if ($cnt == 1) {
0614:             preg_match('/([0-9]{4})\-([0-9]{2})\-([0-9]{2})T([0-9]{2})\:/ui', $info[0]['DateTime'], $arr);
0615:             $dt0  = $info[0]['DateTime'] = sprintf('%d年%d月%d日%d時', $arr[1]$arr[2]$arr[3]$arr[4]);
0616:         }
0617:         $items[$cnt]['label']     = $num;
0618:         $items[$cnt]['label_color'] = COLOR_NAME1;
0619:         $items[$cnt]['label_size'] = 16;
0620:         $items[$cnt]['label_weight'] = 'bold';
0621:         //情報ウィンドウ
0622:         if ($info[0]['AreaClass'] != '') {
0623:             $AreaClass = $info[0]['AreaClass'];
0624:         } else {
0625:             $AreaClass = '-';
0626:         }
0627:         if ($info[0]['IntensityClass'] != '') {
0628:             $IntensityClass = $info[0]['IntensityClass'];
0629:         } else {
0630:             $IntensityClass = '-';
0631:         }
0632:         if ($items[$cnt]['longitude'] >=0) {
0633:             $lng = sprintf('東経%.1f度', $items[$cnt]['longitude']);
0634:         } else {
0635:             $lng = sprintf('西経%.1f度', 0 - $items[$cnt]['longitude']);
0636:         }
0637:         if ($items[$cnt]['latitude'] >=0) {
0638:             $lat = sprintf('北緯%.1f度', $items[$cnt]['latitude']);
0639:         } else {
0640:             $lat = sprintf('南緯%.1f度', 0 - $items[$cnt]['latitude']);
0641:         }
0642:         if (is_numeric($info[0]['Speed'])) {
0643:             $speed = sprintf('時速%dkm', $info[0]['Speed']);
0644:         } else {
0645:             $speed = $info[0]['Speed'];
0646:         }
0647:         if (isset($info[0]['centerWindSpeed']) && is_numeric($info[0]['centerWindSpeed'])) {
0648:             $centerWindSpeed = $info[0]['centerWindSpeed'] . 'メートル';
0649:         } else {
0650:             $centerWindSpeed = '-';
0651:         }
0652:         if (isset($info[0]['maxWindSpeed']) && is_numeric($info[0]['maxWindSpeed'])) {
0653:             $maxWindSpeed = $info[0]['maxWindSpeed'] . 'メートル';
0654:         } else {
0655:             $maxWindSpeed = '-';
0656:         }
0657:         $items[$cnt]['title'] = '';
0658: $items[$cnt]['description'] =<<< EOD
0659: <span style="font-size:120%; font-weight:bold;">{$name}</span><br />大きさ:{$AreaClass}<br />強さ:{$IntensityClass}</br />中心位置:{$lat},{$lng}<br />({$info[0]['Location']})<br />進路:{$info[0]['Direction']}へ{$speed}<br />中心気圧:{$info[0]['Pressure']}hPa<br />中心付近の最大風速:{$centerWindSpeed}<br />最大瞬間風速:{$maxWindSpeed}<br />
0660: EOD;
0661:         //台風情報一覧
0662: $table .=<<< EOD
0663: <tr>
0664: <td>{$name}</td>
0665: <td>{$info[0]['Location']}</td>
0666: <td>{$info[0]['Pressure']}hPa</td>
0667: <td>{$maxWindSpeed}</td>
0668: <td>{$info[0]['Direction']}へ{$speed}</td>
0669: </tr>
0670: 
0671: EOD;
0672:         //3日以内の予報円の中心座標を求める v.2.01
0673:         $m = 1;
0674:         while (isset($info[$m])) {
0675:             if ($info[$m]['kind'] == '予報') {
0676:                 $lat0 = $info[$m]['latitude'];
0677:                 $lng0 = $info[$m]['longitude'];
0678:                 if ($m >= 3)    break;
0679:             }
0680:             $m++;
0681:         }
0682:         $cnt++;
0683:     }
0684: 
0685: $table .=<<< EOD
0686: </table>
0687: 
0688: EOD;
0689:     //有効な台風情報の数
0690:     $count = $cnt - 1;
0691: 
0692:     return array($dt0$lat0$lng0);
0693: }

ユーザー関数が getTyphoonInfo は、台風情報一覧や、マップ上のアイコンをクリックするとあらわれる情報ウィンドウの内容(テキスト)を生成するユーザー関数である。

解説:メイン・プログラム

0819: //気象庁防災情報XMLから台風情報を取得
0820: $infos = array();
0821: $urls = array();
0822: $items = array();
0823: $table = $errmsg = $dt = '';
0824: $count = 0;
0825: $js = FALSE;
0826: $res = getTyphoon($pgc$infos$urls$errmsg);
0827: if ($res == FALSE) {
0828:     $errmsg = $pgc->getError();
0829: else {
0830:     $js = jsTyphoonMap($pgc$infos$errmsg);
0831:     list($dt$latitude$longitude) = getTyphoonInfo($infos$items$table$count);
0832:     if ($count == 0) {
0833:         $longitude = DEF_LONGITUDE;
0834:         $latitude  = DEF_LATITUDE;
0835:         $zoom      = DEF_ZOOM;
0836:         $type      = DEF_TYPE;
0837:     }
0838: }
0839: 
0840: //マップ作成
0841: if (($errmsg == '') && ($js != FALSE)) {
0842:     $jsmap = $pgc->drawJSMap(MAPID$latitude$longitude$type$zoomNULL$itemsMAPSERVICE$js, (MAP_WIDTH / 2));
0843: else {
0844:     $jsmap = $pgc->drawJSMap(MAPID$latitude$longitude$type$zoomNULL$itemsMAPSERVICE);
0845: }
0846: 
0847: $HtmlBody = makeCommonBody($id$dt$jsmap$table$urls$errmsg$count);
0848: 
0849: // 表示処理
0850: echo $HtmlHeader;
0851: echo $HtmlBody;
0852: echo $HtmlFooter;

メイン・プログラムでは、まず、getTyphoon 関数で台風情報を取得し、エラーがなかったら、jsTyphoonMap 関数で描画スクリプトを生成する。
ここまででエラーがなければ、マップを生成する。
エラーがあれば、台風情報を描かずにマップのみ生成する。

活用例

みんなの知識 ちょっと便利帳では、「日本付近の台風情報 - 気象庁発表」のコーナーで、このサンプル・プログラムを活用している。ありがとうございます。

参考サイト

(この項おわり)
header