PHPで台風情報を取得する

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

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

(2021 年 7 月 23 日)不具合修正.
(2021 年 7 月 22 日)台風が 2 つ以上あるときの予報円、過去進路を修正した.予報円の間引き機能を追加した.
(2021 年 7 月 19 日)台風が 2 つ以上あるときの不具合を修正した.
(2021 年 6 月 14 日)マップ画像と一覧表、メッセージを自動投稿するツイート機能を追加した.
(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 が通ったディレクトリに配置すること。
pahooTwitterAPI.phpTwitter APIを利用するクラス pahooTwitterAPI。
使い方は「PHPでTwitterに投稿(ツイート)する」などを参照。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 の登録方法」を参照されたい。

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

0040: //地図描画サービスの選択
0041: //    0:Google
0042: //    2:地理院地図・OSM
0043: define('MAPSERVICE', 2);

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

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

0070: //キャッシュ保持時間(分) 0:キャッシュしない
0071: //気象庁へのアクセス負荷軽減+台風の進路プロット
0072: define('LIFE_CACHE_FEED',   5);         //高頻度 - 随時フィードに対して
0073: define('LIFE_CACHE_FEED_L', 120);       //長期   - 随時フィードに対して
0074: define('LIFE_CACHE_DATA', (60 * 24 * 14));  //台風情報の保持時間(進路プロット)
0075: 
0076: //キャッシュ・ディレクトリ
0077: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
0078: define('DIR_CACHE_FEED',   './pcache_typhoon1/');
0079: define('DIR_CACHE_FEED_L', './pcache_typhoon2/');
0080: 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 の発表時刻によっては予報円の情報が無い。その場合、予報円情報が存在する最も新しい VPTW60 から予報円情報を取り出すことにする。
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時間後予報日時

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

0048: //予報円を間引く条件(km):次の予報円がこれより近ければ描画しない
0049: define('THIN_OUT', 50);
0050: 
0051: //マップの表示サイズ(単位:ピクセル)
0052: define('MAP_WIDTH',  600);
0053: define('MAP_HEIGHT', 480);
0054: //マップID
0055: define('MAPID', 'map_id');
0056: //マップ座標
0057: define('DEF_LATITUDE',  35.0);           //緯度
0058: define('DEF_LONGITUDE', 137.0);          //経度
0059: //define('DEF_TYPE',      'GSISTD');        //マップタイプ
0060: define('DEF_TYPE',     'HYBRID');      //マップタイプ
0061: define('DEF_ZOOM',     5);              //ズーム
0062: //マップ描画色
0063: define('COLOR_NAME1',    '#FF8800');   //台風名称の描画色
0064: define('COLOR_NAME2',    '#0000FF');   //熱低名称の描画色
0065: define('COLOR_LINE',     '#0000FF');   //過去経路の描画色
0066: define('COLOR_WIND1',    '#FFFF00');   //強風域の描画色
0067: define('COLOR_WIND2',    '#FF0000');   //暴風域の描画色
0068: define('COLOR_FORECAST', '#FFFFFF');   //予報円の描画色

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

予報円を間引く条件 THIN_OUT は、台風が停滞していたり、移動速度が極めてゆっくりの場合、予報円の密度が高すぎて地図が見にくくなる。そこで、前回予報円がこの距離以下であれば次の予報円を描かない(間引く)ことができる。0 を代入すれば、すべての予報円を描くようになる。

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

0331: /**
0332:  * 気象庁防災情報XMLから台風に関する情報URLを取得
0333:  * @param   array  $urls    URL格納配列
0334:  * @param   string $errmsg  エラーメッセージ格納用
0335:  * @return  bool TRUE:取得成功/FALSE:取得失敗
0336: */
0337: function jmaGetTyphoonURLs(&$urls, &$errmsg) {
0338:     //URLパターン
0339:     $vptw = '/http\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VPTW6[0-9]+\_[0-9]+\.xml/ui';
0340: 
0341:     //随時フィードの解析
0342:     $cnt = 0;
0343:     $pcc = new pahooCache(LIFE_CACHE_FEEDDIR_CACHE_FEED);
0344:     $xml = $pcc->simplexml_load(FEED);
0345:     //レスポンス・チェック
0346:     if ($pcc->iserror() || !isset($xml->entry)) {
0347:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0348:         return FALSE;
0349:     }
0350:     foreach ($xml->entry as $node) {
0351:         //URLを取得
0352:         if (preg_match($vptw$node->id$arr) > 0) {
0353:             $urls[$cnt] = $arr[0];
0354:             $cnt++;
0355:         }
0356:     }
0357:     $pcc = NULL;
0358: 
0359:     //長期フィードの解析
0360:     $pcc = new pahooCache(LIFE_CACHE_FEED_LDIR_CACHE_FEED_L);
0361:     $xml = $pcc->simplexml_load(FEED_L);
0362:     //レスポンス・チェック
0363:     if ($pcc->iserror() || !isset($xml->entry)) {
0364:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0365:         return FALSE;
0366:     }
0367:     foreach ($xml->entry as $node) {
0368:         //URLを取得
0369:         if (preg_match($vptw$node->id$arr) > 0) {
0370:             if (array_search($arr[0]$urls) === FALSE) {
0371:                 $urls[$cnt] = $arr[0];
0372:                 $cnt++;
0373:             }
0374:         }
0375:     }
0376:     $pcc = NULL;
0377: 
0378:     //エラー・チェック
0379:     if ($cnt == 0) {
0380:         $errmsg = '直近の台風情報はありません';
0381:         return FALSE;
0382:     }
0383: 
0384:     //URLを日時の新しい順にソート
0385:     rsort($urls);
0386: 
0387:     return TRUE;
0388: }

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

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

0390: /**
0391:  * 新しい台風報かどうか
0392:  * @param   object $xml     気象庁防災情報XML
0393:  * @param   array  $items   台風情報を格納する配列
0394:  * @return  bool TRUE:新しい情報
0395: */
0396: function isNewTyphoon($xml$items) {
0397:     $res = FALSE;
0398: 
0399:     if (isset($xml->Body->MeteorologicalInfos->MeteorologicalInfo->Item->Kind->Property->TyphoonNamePart->Number)) {
0400:         $num = (string)$xml->Body->MeteorologicalInfos->MeteorologicalInfo->Item->Kind->Property->TyphoonNamePart->Number;
0401:         if ($num != '') {
0402:             if (! isset($items[$num])) {
0403:                 $res = TRUE;
0404:             }
0405:         }
0406:     }
0407: 
0408:     return $res;
0409: }

0411: /**
0412:  * 台風報取得(気象庁防災情報XMLから)
0413:  * @param   object $pgc     pahooGeoCodeオブジェクト
0414:  * @param   array  $items   台風情報を格納する配列
0415:  * @param   string $urls    情報XMLのURLを格納する配列
0416:  * @param   string $errmsg  エラーメッセージ格納用
0417:  * @return  bool TRUE:取得成功/FALSE:失敗
0418: */
0419: function getTyphoon($pgc, &$items, &$urls, &$errmsg) {
0420:     //名前空間
0421:     define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
0422:     //マッチングパターン
0423:     $pat2 = '/([\+\-][0-9]{1,2}\.[0-9]+)([\+\-][0-9]{1,3}\.[0-9]+)/ui';   //緯度・経度
0424:     $pat3 = '/(予報)[  ]*([01234567890-9]+)時間後/ui';   //推定|予報
0425:     //予報円は取得済みか否か
0426:     $flag_forecast = array();
0427: 
0428:     //オブジェクト生成
0429:     $pcc = new pahooCache(LIFE_CACHE_DATADIR_CACHE_DATA);
0430: 
0431:     //最新の台風に関する情報URLを取得
0432:     jmaGetTyphoonURLs($urls$errmsg);
0433:     if ($errmsg != '')  return FALSE;
0434: 
0435:     foreach ($urls as $key=>$vptw) {
0436:         //台風情報の取得
0437:         $xml = $pcc->simplexml_load($vptw);
0438:         //レスポンス・チェック
0439:         if ($pcc->iserror() || !isset($xml->Body->MeteorologicalInfos)) {
0440:             $errmsg = '気象庁防災情報XMLから台風情報を取得できません';
0441:             return FALSE;
0442:         }
0443:         $flag_f = FALSE;     //予報円取得済みか否か
0444: 
0445:         //最新の台風情報
0446:         if (isNewTyphoon($xml$items)) {
0447:             foreach ($xml->Body->MeteorologicalInfos as $infos) {
0448:                 $cnt = 0;
0449:                 foreach ($infos->MeteorologicalInfo as $info) {
0450:                     //実況
0451:                     if ($info->DateTime['type'] == '実況') {
0452:                         foreach ($info->Item->Kind as $kind) {
0453:                             if (isset($kind->Property->Type)) {
0454:                                 //呼称
0455:                                 if ($kind->Property->Type == '呼称') {
0456:                                     $num = (string)$kind->Property->TyphoonNamePart->Number;
0457:                                     if ($num == '')        break;
0458:                                     $items[$num]['Name'] = (string)$kind->Property->TyphoonNamePart->Name;
0459:                                     $items[$num]['NameKana'] = (string)$kind->Property->TyphoonNamePart->NameKana;
0460:                                     $items[$num][$cnt]['kind'] = '実況';
0461:                                     $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0462:                                     //その台風の予報円は未取得
0463:                                     $flag_forecast[$num] = FALSE;
0464:                                     //古い台風情報かどうか
0465:                                     $tt = time() - strtotime($info->DateTime);
0466:                                     $items[$num]['Valid'] = ($tt < SCRAP_TIME) ? TRUE : FALSE;
0467:                                 //階級
0468:                                 } else if ($kind->Property->Type == '階級') {
0469:                                     $node = $kind->Property->ClassPart->children(JMX_EB);
0470:                                     $items[$num][$cnt]['TyphoonClass'] = (string)$node->TyphoonClass;
0471:                                     $items[$num][$cnt]['AreaClass'] = (string)$node->AreaClass;
0472:                                     $items[$num][$cnt]['IntensityClass'] = (string)$node->IntensityClass;
0473:                                 //中心
0474:                                 } else if ($kind->Property->Type == '中心') {
0475:                                     $node = $kind->Property->CenterPart->children(JMX_EB);
0476:                                     //中心位置
0477:                                     $items[$num][$cnt]['Location'] = (string)$kind->Property->CenterPart->Location;
0478:                                     foreach ($node->Coordinate as $val) {
0479:                                         if (preg_match($pat2$val$arr) > 0) {
0480:                                             $items[$num][$cnt]['latitude'] = (float)$arr[1];
0481:                                             $items[$num][$cnt]['longitude'] = (float)$arr[2];
0482:                                         }
0483:                                     }
0484:                                     //移動速度
0485:                                     $items[$num][$cnt]['Direction'] = (string)$node->Direction;
0486:                                     foreach ($node->Speed as $val) {
0487:                                         if (isset($val->attributes()['condition'])) {
0488:                                             $items[$num][$cnt]['Speed'] = (string)$val->attributes()['condition'];
0489:                                         } else if ($val->attributes()['unit'] == 'km/h') {
0490:                                             $items[$num][$cnt]['Speed'] = (string)$val;
0491:                                         }
0492:                                     }
0493:                                     //中心気圧
0494:                                     $items[$num][$cnt]['Pressure'] = (int)$node->Pressure;
0495:                                 //風
0496:                                 } else if ($kind->Property->Type == '') {
0497:                                     $node = $kind->Property->WindPart->children(JMX_EB);
0498:                                     foreach ($node->WindSpeed as $val) {
0499:                                         if (($val->attributes()['condition'] == '中心付近') && ($val->attributes()['unit'] == 'm/s')) {
0500:                                             $items[$num][$cnt]['centerWindSpeed'] = (int)$val;
0501:                                         } else if (($val->attributes()['type'] == '最大瞬間風速') && ($val->attributes()['unit'] == 'm/s')) {
0502:                                             $items[$num][$cnt]['maxWindSpeed'] = (int)$val;
0503:                                         }
0504:                                     }
0505:                                     //暴風域・強風域
0506:                                     foreach ($kind->Property->WarningAreaPart as $val) {
0507:                                         $key = (string)$val['type'];
0508:                                         //半径
0509:                                         $node = $val->children(JMX_EB);
0510:                                         $n = 0;
0511:                                         foreach ($node->Circle->Axes->Axis as $axis) {
0512:                                             if (isset($axis->Direction) && ($axis->Direction != '')) {
0513:                                                 $items[$num][$cnt][$key][$n]['direction'] = (string)$axis->Direction;
0514:                                             } else {
0515:                                                 $items[$num][$cnt][$key][$n]['direction'] = (string)$axis->Direction->attributes()['condition'];
0516:                                             }
0517:                                             foreach ($axis->Radius as $val) {
0518:                                                 if ($val->attributes()['unit'] == 'km') {
0519:                                                     $items[$num][$cnt][$key][$n]['radius'] = (int)$val;
0520:                                                 }
0521:                                             }
0522:                                             $n++;
0523:                                         }
0524:                                     }
0525:                                 }
0526:                             }
0527:                         }
0528:                     //予報
0529:                     } else if (preg_match($pat3, (string)$info->DateTime['type'], $arr) > 0) {
0530:                         if ($num == '')        break;       //ver.2.04
0531:                         $cnt++;
0532:                         $items[$num][$cnt]['kind'] = '予報';
0533:                         $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0534:                         $flag_f = TRUE;      //予報円取得済み
0535:                         foreach ($info->Item->Kind as $kind) {
0536:                             //階級
0537:                             if ($kind->Property->Type == '階級') {
0538:                                 $node = $kind->Property->ClassPart->children(JMX_EB);
0539:                                 $items[$num][$cnt]['TyphoonClass'] = (string)$node->TyphoonClass;
0540:                                 $items[$num][$cnt]['AreaClass'] = isset($node->AreaClass) ? (string)$node->AreaClass : '';
0541:                                 $items[$num][$cnt]['IntensityClass'] = isset($node->IntensityClass) ? (string)$node->IntensityClass : '';
0542:                             //中心位置(予報円)
0543:                             } else if ($kind->Property->Type == '中心') {
0544:                                 $node = $kind->Property->CenterPart->ProbabilityCircle->children(JMX_EB);
0545:                                 foreach ($node->BasePoint as $val) {
0546:                                     if (preg_match($pat2$val$arr) > 0) {
0547:                                         $items[$num][$cnt]['latitude'] = (float)$arr[1];
0548:                                         $items[$num][$cnt]['longitude'] = (float)$arr[2];
0549:                                     }
0550:                                 }
0551:                                 //半径
0552:                                 $n = 0;
0553:                                 foreach ($node->Axes->Axis as $axis) {
0554:                                     if (isset($axis->Direction) && ($axis->Direction != '')) {
0555:                                         $items[$num][$cnt]['予報円'][$n]['direction'] = (string)$axis->Direction;
0556:                                     } else {
0557:                                         $items[$num][$cnt]['予報円'][$n]['direction'] = (string)$axis->Direction->attributes()['condition'];
0558:                                     }
0559:                                     foreach ($axis->Radius as $val) {
0560:                                         if ($val->attributes()['unit'] == 'km') {
0561:                                             $items[$num][$cnt]['予報円'][$n]['radius'] = (int)$val;
0562:                                         }
0563:                                     }
0564:                                     $n++;
0565:                                 }
0566:                             //風
0567:                             } else if ($kind->Property->Type == '') {
0568:                                 $node = $kind->Property->WindPart->children(JMX_EB);
0569:                                 foreach ($node->WindSpeed as $val) {
0570:                                     if (($val->attributes()['condition'] == '中心付近') && ($val->attributes()['unit'] == 'm/s')) {
0571:                                         $items[$num][$cnt]['centerWindSpeed'] = (int)$val;
0572:                                     } else if (($val->attributes()['type'] == '最大瞬間風速') && ($val->attributes()['unit'] == 'm/s')) {
0573:                                         $items[$num][$cnt]['maxWindSpeed'] = (int)$val;
0574:                                     }
0575:                                 }
0576:                             }
0577:                         }
0578:                     }
0579:                 }
0580:             }
0581:         //過去の台風情報
0582:         } else {
0583:             foreach ($xml->Body->MeteorologicalInfos as $infos) {
0584:                 foreach ($infos->MeteorologicalInfo as $info) {
0585:                     //過去の位置
0586:                     if ($info->DateTime['type'] == '実況') {
0587:                         foreach ($info->Item->Kind as $kind) {
0588:                             if (isset($kind->Property->Type)) {
0589:                                 //呼称
0590:                                 if ($kind->Property->Type == '呼称') {
0591:                                     $num = (string)$kind->Property->TyphoonNamePart->Number;
0592:                                     if ($num == '')        break;
0593:                                     $cnt = 0;
0594:                                     while (isset($items[$num][$cnt]))   $cnt++;
0595:                                     $items[$num][$cnt]['kind'] = '過去';
0596:                                     $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0597:                                 //中心
0598:                                 } else if ($kind->Property->Type == '中心') {
0599:                                     $node = $kind->Property->CenterPart->children(JMX_EB);
0600:                                     //中心位置
0601:                                     foreach ($node->Coordinate as $val) {
0602:                                         if (preg_match($pat2$val$arr) > 0) {
0603:                                             $items[$num][$cnt]['latitude'] = (float)$arr[1];
0604:                                             $items[$num][$cnt]['longitude'] = (float)$arr[2];
0605:                                         }
0606:                                     }
0607:                                 }
0608:                             }
0609:                         }
0610:                     //予報
0611:                     } else if (isset($flag_forecast[$num]) && ! $flag_forecast[$num] && (preg_match($pat3, (string)$info->DateTime['type'], $arr) > 0)) {
0612:                         if ($num == '')        break;       //ver.2.04
0613:                         $cnt++;
0614:                         $items[$num][$cnt]['kind'] = '予報';
0615:                         $items[$num][$cnt]['DateTime'] = (string)$info->DateTime;
0616:                         $flag_f = TRUE;      //予報円取得済み
0617:                         foreach ($info->Item->Kind as $kind) {
0618:                             //階級
0619:                             if ($kind->Property->Type == '階級') {
0620:                                 $node = $kind->Property->ClassPart->children(JMX_EB);
0621:                                 $items[$num][$cnt]['TyphoonClass'] = (string)$node->TyphoonClass;
0622:                                 $items[$num][$cnt]['AreaClass'] = isset($node->AreaClass) ? (string)$node->AreaClass : '';
0623:                                 $items[$num][$cnt]['IntensityClass'] = isset($node->IntensityClass) ? (string)$node->IntensityClass : '';
0624:                             //中心位置(予報円)
0625:                             } else if ($kind->Property->Type == '中心') {
0626:                                 $node = $kind->Property->CenterPart->ProbabilityCircle->children(JMX_EB);
0627:                                 foreach ($node->BasePoint as $val) {
0628:                                     if (preg_match($pat2$val$arr) > 0) {
0629:                                         $items[$num][$cnt]['latitude'] = (float)$arr[1];
0630:                                         $items[$num][$cnt]['longitude'] = (float)$arr[2];
0631:                                     }
0632:                                 }
0633:                                 //半径
0634:                                 $n = 0;
0635:                                 foreach ($node->Axes->Axis as $axis) {
0636:                                     if (isset($axis->Direction) && ($axis->Direction != '')) {
0637:                                         $items[$num][$cnt]['予報円'][$n]['direction'] = (string)$axis->Direction;
0638:                                     } else {
0639:                                         $items[$num][$cnt]['予報円'][$n]['direction'] = (string)$axis->Direction->attributes()['condition'];
0640:                                     }
0641:                                     foreach ($axis->Radius as $val) {
0642:                                         if ($val->attributes()['unit'] == 'km') {
0643:                                             $items[$num][$cnt]['予報円'][$n]['radius'] = (int)$val;
0644:                                         }
0645:                                     }
0646:                                     $n++;
0647:                                 }
0648:                             //風
0649:                             } else if ($kind->Property->Type == '') {
0650:                                 $node = $kind->Property->WindPart->children(JMX_EB);
0651:                                 foreach ($node->WindSpeed as $val) {
0652:                                     if (($val->attributes()['condition'] == '中心付近') && ($val->attributes()['unit'] == 'm/s')) {
0653:                                         $items[$num][$cnt]['centerWindSpeed'] = (int)$val;
0654:                                     } else if (($val->attributes()['type'] == '最大瞬間風速') && ($val->attributes()['unit'] == 'm/s')) {
0655:                                         $items[$num][$cnt]['maxWindSpeed'] = (int)$val;
0656:                                     }
0657:                                 }
0658:                             }
0659:                         }
0660:                     }
0661:                 }
0662:             }
0663:         }
0664:         //その台風の予報円は取得済み
0665:         if ($flag_f) {
0666:             $flag_forecast[$num] = TRUE;
0667:         }
0668:     }
0669:     //オブジェクト解放
0670:     $pcc = NULL;
0671: 
0672:     return TRUE;
0673: }

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

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

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

前述の通り、VPTW60 の発表日時によっては予報円情報を含まないことがある。
そこで、台風毎に配列 $flag_forecast に予報円を取得したかどうかのフラグを持たせる。もし isNewTyphoon で取得した最新の VPTW60 に予報円情報が無ければ、過去の台風情報から予報円情報を取得する。
このため、ほぼ同じ予報取得プロセスが 2箇所に書く格好になっており、美しさに欠けてしまった。

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

0720: /**
0721:  * 暴風域、強風域、予報円の描画スクリプトを生成する
0722:  * @param   object $pgc   pahooGeoCodeオブジェクト
0723:  * @param   array  $infos  台風情報
0724:  * @return  string スクリプト/FALSE:生成失敗
0725: */
0726: function jsTyphoonMap($pgc$infos) {
0727:     $js = '';
0728:     foreach ($infos as $info) {
0729:         $key = 0;
0730:         //台風以外ならスキップ
0731:         if (! isset($info[$key]['TyphoonClass']))    continue;    //v.2.11
0732:         if (preg_match('/台風/ui', $info[$key]['TyphoonClass']) == 0)    continue;
0733:         //古い台風情報ならスキップ
0734:         if ($info['Valid'] == FALSE)    continue;
0735: 
0736:         //暴風域
0737:         list($latitude$longitude$radius) = shiftCircle($pgc$info[$key]['latitude'], $info[$key]['longitude'], $info[$key]['暴風域']);
0738:         $radius *= 1000;
0739:         $js .= $pgc->jsCircle($longitude$latitude$radiusCOLOR_WIND2, '1.0', 2, MAPSERVICE);
0740:         //強風域
0741:         list($latitude$longitude$radius) = shiftCircle($pgc$info[$key]['latitude'], $info[$key]['longitude'], $info[$key]['強風域']);
0742:         $radius *= 1000;
0743:         $js .= $pgc->jsCircle($longitude$latitude$radiusCOLOR_WIND1, '1.0', 2, MAPSERVICE);
0744: 
0745:         $key = 1;
0746:         $cnt = 1;
0747:         $lng_0 = $points[0]['longitude'] = $info[0]['longitude'];
0748:         $lat_0 = $points[0]['latitude']  = $info[0]['latitude'];
0749:         while (isset($info[$key])) {
0750:             //予報円
0751:             if ($info[$key]['kind'] == '予報') {
0752:                 //予報円を間引くかどうか
0753:                 $dd = $pgc->greatCircleDistance($lng_0$lat_0$info[$key]['longitude'], $info[$key]['latitude']);
0754:                 if ($dd > THIN_OUT) {
0755:                     list($latitude$longitude$radius) = shiftCircle($pgc$info[$key]['latitude'], $info[$key]['longitude'], $info[$key]['予報円']);
0756:                     $radius *= 1000;
0757:                     $js .= $pgc->jsCircle($longitude$latitude$radiusCOLOR_FORECAST, '1.0', 2, MAPSERVICE);
0758:                     preg_match('/[0-9]{4}\-[0-9]{2}\-([0-9]{2})T([0-9]{2})/ui', $info[$key]['DateTime'], $arr);
0759:                     $dt = sprintf('%d日%d時', $arr[1]$arr[2]);
0760:                     list($lat$lng) = $pgc->getPointDistance($longitude$latitude, 0 - $radius - 30000, 9);
0761:                     $js .= $pgc->jsLabel($lat$lng$dt, 12, COLOR_FORECAST, 'normal', MAPSERVICE);
0762:                     $lat_0 = $info[$key]['latitude'];
0763:                     $lng_0 = $info[$key]['longitude'];
0764:                 }
0765: 
0766:             //過去の位置
0767:             } else {
0768:                 $points[$cnt]['longitude'] = $info[$key]['longitude'];
0769:                 $points[$cnt]['latitude']  = $info[$key]['latitude'];
0770:                 $cnt++;
0771:             }
0772:             $key++;
0773:         }
0774:         //過去の移動経路
0775:         if ($js != '') {
0776:             $js .= $pgc->jsLine($pointsCOLOR_LINE, '1.0', 3, MAPSERVICE);
0777:             $points = array();
0778:             $cnt = 0;
0779:             if (isset($info[$key]['longitude'])) {
0780:                 $points[0]['longitude'] = $info[$key]['longitude'];
0781:                 $points[0]['latitude']  = $info[$key]['latitude'];
0782:             }
0783:         }
0784:     }
0785: 
0786:     //HTMLの画像化
0787:     $js .= js_html2image();
0788: 
0789:     return $js;
0790: }

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

前回の予報円の中心座標 ($lat_0, $lng_0) (初回は現在の台風の中心座標)からの大圏航路距離をメソッド greatCircleDistance によって計算し、予報円の間引き条件 THIN_OUT 以下であれば予報円を描かない。

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

0792: /**
0793:  * 台風情報からマップ描画用情報を生成する
0794:  * @param   array  $infos  台風情報
0795:  * @param   array  $items  マップ描画用情報を格納
0796:  * @param   string $table  HTML文(表形式)を格納
0797:  * @param   int    $count  有効な台風情報の数
0798:  * @return  array(日時,緯度,経度) 発表日時,予報円の最後の中心座標
0799: */
0800: function getTyphoonInfo($infos, &$items, &$table, &$count) {
0801:     //台風情報一覧
0802: $table =<<< EOD
0803: <table class="plists">
0804: <th>名称</th>
0805: <th>位置</th>
0806: <th>中心気圧</th>
0807: <th>最大瞬間風速</th>
0808: <th>進路</th>
0809: </tr>
0810: 
0811: EOD;
0812: 
0813:     $dt0 = $lat0 = $lng0 = FALSE;
0814:     $cnt = 1;
0815:     foreach ($infos as $nn=>$info) {                         //v.2.11
0816:         $key = 0;                                                //v.2.11
0817:         if (! isset($info[$key]['TyphoonClass']))    continue;    //v.2.11
0818:         if (preg_match('/台風/ui', $info[0]['TyphoonClass']) > 0) {
0819:             $num = sprintf('台%d', (int)substr($nn, 2, 2));  //v.2.11
0820:             $name = sprintf('台風%d号(%s)', (int)substr($nn, 2, 2), $info['NameKana']);
0821:         } else {
0822:             continue;
0823:         }
0824:         //古い台風情報ならスキップ
0825:         if ($info['Valid'] == FALSE)    continue;
0826: 
0827:         $items[$cnt]['longitude'] = $info[0]['longitude'];
0828:         $items[$cnt]['latitude']  = $info[0]['latitude'];
0829:         if ($cnt == 1) {
0830:             preg_match('/([0-9]{4})\-([0-9]{2})\-([0-9]{2})T([0-9]{2})\:/ui', $info[0]['DateTime'], $arr);
0831:             $dt0  = $info[0]['DateTime'] = sprintf('%d年%d月%d日%d時', $arr[1]$arr[2]$arr[3]$arr[4]);
0832:         }
0833:         $items[$cnt]['label']     = $num;
0834:         $items[$cnt]['label_color'] = COLOR_NAME1;
0835:         $items[$cnt]['label_size'] = 16;
0836:         $items[$cnt]['label_weight'] = 'bold';
0837:         //情報ウィンドウ
0838:         if ($info[0]['AreaClass'] != '') {
0839:             $AreaClass = $info[0]['AreaClass'];
0840:         } else {
0841:             $AreaClass = '-';
0842:         }
0843:         if ($info[0]['IntensityClass'] != '') {
0844:             $IntensityClass = $info[0]['IntensityClass'];
0845:         } else {
0846:             $IntensityClass = '-';
0847:         }
0848:         if ($items[$cnt]['longitude'] >=0) {
0849:             $lng = sprintf('東経%.1f度', $items[$cnt]['longitude']);
0850:         } else {
0851:             $lng = sprintf('西経%.1f度', 0 - $items[$cnt]['longitude']);
0852:         }
0853:         if ($items[$cnt]['latitude'] >=0) {
0854:             $lat = sprintf('北緯%.1f度', $items[$cnt]['latitude']);
0855:         } else {
0856:             $lat = sprintf('南緯%.1f度', 0 - $items[$cnt]['latitude']);
0857:         }
0858:         if ($info[0]['Direction'] != '') {
0859:             $directioin = $info[0]['Direction'] . '';
0860:         } else {
0861:             $directioin = '';
0862:         }
0863:         if (is_numeric($info[0]['Speed'])) {
0864:             $speed = sprintf('時速%dkm', $info[0]['Speed']);
0865:         } else {
0866:             $speed = $info[0]['Speed'];
0867:         }
0868:         if (isset($info[0]['centerWindSpeed']) && is_numeric($info[0]['centerWindSpeed'])) {
0869:             $centerWindSpeed = $info[0]['centerWindSpeed'] . 'メートル';
0870:         } else {
0871:             $centerWindSpeed = '-';
0872:         }
0873:         if (isset($info[0]['maxWindSpeed']) && is_numeric($info[0]['maxWindSpeed'])) {
0874:             $maxWindSpeed = $info[0]['maxWindSpeed'] . 'メートル';
0875:         } else {
0876:             $maxWindSpeed = '-';
0877:         }
0878:         $items[$cnt]['title'] = '';
0879: $items[$cnt]['description'] =<<< EOD
0880: <span style="font-size:120%; font-weight:bold;">{$name}</span><br />大きさ:{$AreaClass}<br />強さ:{$IntensityClass}</br />中心位置:{$lat},{$lng}<br />({$info[0]['Location']})<br />進路:{$directioin}{$speed}<br />中心気圧:{$info[0]['Pressure']}hPa<br />中心付近の最大風速:{$centerWindSpeed}<br />最大瞬間風速:{$maxWindSpeed}<br />
0881: EOD;
0882:         //台風情報一覧
0883: $table .=<<< EOD
0884: <tr>
0885: <td>{$name}</td>
0886: <td>{$info[0]['Location']}</td>
0887: <td>{$info[0]['Pressure']}hPa</td>
0888: <td>{$maxWindSpeed}</td>
0889: <td>{$directioin}{$speed}</td>
0890: </tr>
0891: 
0892: EOD;
0893:         //3日以内の予報円の中心座標を求める v.2.01
0894:         $m = 1;
0895:         while (isset($info[$m])) {
0896:             if ($info[$m]['kind'] == '予報') {
0897:                 $lat0 = $info[$m]['latitude'];
0898:                 $lng0 = $info[$m]['longitude'];
0899:                 if ($m >= 3)    break;
0900:             }
0901:             $m++;
0902:         }
0903:         $cnt++;
0904:     }
0905: 
0906: $table .=<<< EOD
0907: </table>
0908: 
0909: EOD;
0910:     //有効な台風情報の数
0911:     $count = $cnt - 1;
0912: 
0913:     return array($dt0$lat0$lng0);
0914: }

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

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

1054: //気象庁防災情報XMLから台風情報を取得
1055: $infos = array();
1056: $urls  = array();
1057: $items = array();
1058: $dt    = $date->format('Y年m月d日H時');
1059: $table = $errmsg = '';
1060: $count = 0;
1061: $js    = FALSE;
1062: $ret   = getTyphoon($pgc$infos$urls$errmsg);
1063: if ($ret == FALSE) {
1064:     $errmsg = $pgc->getError();
1065: else {
1066:     $js = jsTyphoonMap($pgc$infos$errmsg);
1067:     list($dt$latitude$longitude) = getTyphoonInfo($infos$items$table$count);
1068:     if ($count == 0) {
1069:         $longitude = DEF_LONGITUDE;
1070:         $latitude  = DEF_LATITUDE;
1071:         $zoom      = DEF_ZOOM;
1072:         $type      = DEF_TYPE;
1073:         $dt        = $date->format('Y年m月d日H時');
1074:     }
1075: }
1076: 
1077: //マップ作成
1078: if (($errmsg == '') && ($js != FALSE)) {
1079:     $jsmap = $pgc->drawJSMap(MAPID$latitude$longitude$type$zoomNULL$itemsMAPSERVICE$js, (MAP_WIDTH / 2));
1080: else {
1081:     $jsmap = $pgc->drawJSMap(MAPID$latitude$longitude$type$zoomNULL$itemsMAPSERVICE);
1082: }
1083: 
1084: //ツイート機能
1085: $message =<<< EOT
1086: 🌀台風情報 {$dt}現在
1087: 
1088: (ご参考)PHPで台風情報を取得する https://www.pahoo.org/e-soul/webtech/php05/php05-15-01.shtm #台風 #台風情報 #nhk #ntv #tbs #fujitv #tvasahi
1089: 
1090: EOT;
1091: mediaTweet($message$res);
1092: 
1093: //HTML BODY作成
1094: $HtmlBody = makeCommonBody($dt$jsmap$table$urls$res$errmsg$count);
1095: 
1096: //オブジェクト解放
1097: $pgc  = NULL;
1098: $date = NULL;
1099: 
1100: //ブラウザ表示処理
1101: echo $HtmlHeader;
1102: echo $HtmlBody;
1103: echo $HtmlFooter;

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

解説:ツイート機能

表示した天気図(マップ)を画像として、メッセージと一緒にボタン 1 つでツイートする機能を追加した。流れは「PHP で COVID-19 情報をグラフ表示」で解説したとおりである。

0034: //ツイート・ボタン  TRUE:有効,FALSE:無効
0035: define('TWITTER', FALSE);
0036: 
0037: //画像化したいオブジェクト
0038: define('TARGET', 'target');

ツイート機能を使うかどうかは、定数 TWITTER で指定する。
FALSE なら、pahooTwitterAPI クラスを読み込まず、ツイート・ボタンも表示しない。ツイート・ボタンの作成については、「HTML と CSS でさまざまなアイコンを表示する」を参照してほしい。
画像化したいオブジェクト(ID 名)は定数 TARGET で指定する。

解説:html2canvasライブラリ

0122: <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
0123: 
0124: <style>
0125: p.werror {
0126:     color: red;
0127: }

HTML表示を画像化するために、html2canvas ライブラリを利用する。このライブラリは Niklas von Hertzen 氏によって開発されたもので、ライセンスは MIT となっている。
画像化を実行する JavaScript関数は html2canvas である。

1009: <div id="{$target}" name="{$target}" style="width:{$width2}px;">
1010: <p>
1011: 🌀台風情報 {$dt}現在{$res2}
1012: &nbsp;{$tweet}
1013: </p>
1014: <div id="{$mapid}" style="width:{$width}px; height:{$height}px;"></div>
1015: {$table}
1016: </div>

画像化するオブジェクトは、<div id="{$target}"> で指定する範囲である。
レンダリングエンジンによって違うのかもしれないが、html2canvas ライブラリによって画像化される範囲が実際よりやや小さいため、あえてマップ領域より、幅を 20 ピクセル大きくした範囲を画像化範囲としている。

0269: /**
0270:  * HTMLオブジェクトの画像化
0271:  * @param   なし
0272:  * @return  string JavaScriptコード
0273: */
0274: function js_html2image() {
0275:     $target = TARGET;
0276:     $js = '';
0277: 
0278:     //Googleマップの場合
0279:     if (MAPSERVICE == 0) {
0280: $js .=<<< EOT
0281: google.maps.event.addListener(map, 'tilesloaded', function() {
0282:     var capture = document.querySelector('#{$target}');
0283:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
0284:         var base64 = canvas.toDataURL('image/png');       //画像化
0285:         $('#base64').val(base64);
0286:     });
0287: });
0288: 
0289: EOT;
0290: 
0291:     //Leafletの場合(ブラウザによってはうまく動作しない)
0292:     } else {
0293: $js .=<<< EOT
0294: HTMLCanvasElement.prototype.getContext = function(origFn) {
0295:     return function(type, attribs) {
0296:         attribs = attribs || {};
0297:         attribs.preserveDrawingBuffer = true;
0298:         return origFn.call(this, type, attribs);
0299:     };
0300: } (HTMLCanvasElement.prototype.getContext);
0301: 
0302: //HTML画像化イベント登録
0303: function html2image() {
0304:     var capture = document.querySelector('#{$target}');
0305:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
0306:         var base64 = canvas.toDataURL('image/png');       //画像化
0307:         $('#base64').val(base64);
0308:     });
0309: };
0310: 
0311: //ズーム変更イベント
0312: map.on('zoomend', function() {
0313:     html2image();
0314: });
0315: 
0316: //マップ移動イベント
0317: map.on('moveend', function() {
0318:     html2image();
0319: });
0320: 
0321: //ズーム変更イベント発生
0322: var zoom = map.getZoom();
0323: map.setZoom(zoom);
0324: 
0325: EOT;
0326:     }
0327: 
0328:     return $js;
0329: }

html2canvas を呼び出すタイミングだが、Google マップの場合は tilesloadedイベントにフックする。
Leaflet の場合、これに相当するイベントがないため、ズーム変更完了イベントにフックし、強制的にズーム変更イベントを発生させるタイミングで画像化する。しかし、この方法だとブラウザによって、マップ画像が無い状態で画像化されてしまうことがあるようだ。もし対策方法をご存じの方がいたらお知らせいただきたい。
これらを JavaScript として生成するユーザー関数が js_html2image である。

準備:pahooTwitterAPI クラス

0015: class pahooTwitterAPI {
0016:     var $webapi;     //直前に呼び出したWebAPI URL
0017:     var $error;      //エラーフラグ
0018:     var $errmsg;     //エラーメッセージ
0019:     var $errcode;        //エラーコード
0020:     var $responses;  //直前の結果(配列)
0021: 
0022:     //OAuth用パラメータ
0023:     // https://apps.twitter.com/
0024:     var $TWTR_CONSUMER_KEY    = '***************';  //Cunsumer key
0025:     var $TWTR_CONSUMER_SECRET = '***************';  //Consumer secret
0026:     var $TWTR_ACCESS_KEY      = '***************';  //Access Token (oauth_token)
0027:     var $TWTR_ACCESS_SECRET   = '***************';  //Access Token Secret (oauth_token_secret)

Twitter API」を利用するために、API keyAPI secret keyAccess tokenAccess token secret が必要で、入手方法は「Twitter API - WebAPI の登録方法」を参照されたい。

解説:メディア付き投稿(RAWデータ)

0385: /**
0386:  * メディア付き投稿(RAWデータ)
0387:  * @param   string $message 投稿メッセージ(UTF-8限定)
0388:  * @param   array  $raws    メディアデータ(RAWデータ配列)
0389:  * @return  bool TRUE:リクエスト成功/FALSE:失敗
0390: */
0391: function tweet_media_raw($message$raws) {
0392:     static $url_upload    = 'https://upload.twitter.com/1.1/media/upload.json';
0393:     static $url_tweet     = 'https://api.twitter.com/1.1/statuses/update.json';
0394:     static $method        = 'POST' ;
0395: 
0396:     //メディアのアップロード
0397:     $media_ids = '';
0398:     $cnt = 0;
0399:     foreach ($raws as $raw) {
0400:         $media_id = $this->upload($url_upload, 'POST', 'media', $raw);
0401:         if ($media_id == NULL)      break;
0402:         if ($cnt > 0)   $media_ids .= ',';
0403:         $media_ids .= $media_id;
0404:         $cnt++;
0405:         if ($cnt > 3)   break;       //最大4つまで
0406:     }
0407: 
0408:     //ツイート
0409:     if (! $this->error) {
0410:         $option = array('status' => $message, 'media_ids' => $media_ids);
0411:         $res = $this->request_user($url_tweet$method$option);
0412:     } else {
0413:         $res = FALSE;
0414:     }
0415: 
0416:     return $res;
0417: }

メッセージと画像を同時にツイートするのに、「PHP で Twitter に画像付きメッセージ投稿」で作ったメソッド tweet_media を改良し、引数に画像バイナリデータ(RAW データ)を渡して TwitterAPI を呼び出す tweet_media_raw メソッドを用意した。

解説:ツイート処理

0244: /**
0245:  * ツイート処理
0246:  * @param   string $message 投稿文
0247:  * @param   string $res     応答メッセージ格納用
0248:  * @return  bool TRUE:成功/FALSE:失敗または未処理
0249: */
0250: function mediaTweet($message, &$res) {
0251:     if (! TWITTER)   return FALSE;
0252: 
0253:     $ret = TRUE;
0254:     if (isset($_POST['base64']) && ($_POST['base64'] != '')) {
0255:         $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
0256:         $raws = array(base64_decode($base64));
0257:         $ptw = new pahooTwitterAPI();
0258:         $ptw->tweet_media_raw($message$raws);
0259:         $errmsg = $ptw->errmsg;
0260:         $ret = ! $ptw->error;
0261:         $ptw = NULL;
0262:         if ($ret) {
0263:             $res = 'ツイートしました';
0264:         }
0265:     }
0266:     return $ret;
0267: }

メッセージと画像をツイート処理するのはサーバ側の PHP ユーザー関数 mediaTweet で処理する。
ブラウザから POST で受け取った画像データ $_POST['base64'&$x5D; は BASE64 でデコードされており、冒頭に余計なヘッダ情報が付いているので、このヘッダ情報を除き( preg_replace )、バイナリデータにデコードする( base64_decode )。
続いて pahooTwitterAPI クラスを呼び出し、tweet_media_raw メソッドを使って、メッセージと画像を一気にツイートする。

1084: //ツイート機能
1085: $message =<<< EOT
1086: 🌀台風情報 {$dt}現在
1087: 
1088: (ご参考)PHPで台風情報を取得する https://www.pahoo.org/e-soul/webtech/php05/php05-15-01.shtm #台風 #台風情報 #nhk #ntv #tbs #fujitv #tvasahi
1089: 
1090: EOT;
1091: mediaTweet($message$res);

ユーザー関数 mediaTweet を呼び出しメインプログラム側のコードは上述の通りである。メッセージの内容は自由に変更していただいて構わない。

活用例

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

参考サイト

(この項おわり)
header