PHPで最近の地震情報を表示する

(1/1)
東日本大震災の余震と見られる地震が収まらない。
そこで今回は、気象庁防災情報 XMLから、直近の地震情報を取得する PHP スクリプトを作成してみることにする。
余震が多いことが分かりやすいように、求めたい震源の数を任意に指定できるように改良した。

(2021 年 6 月 14 日)マップ画像と一覧表、メッセージを自動投稿するツイート機能を追加した.
(2021 年 6 月 5 日)震源の深さに「約」付記,その他表示改善
(2021 年 5 月 23 日)バグ修正:get_earthquake() 深さ不明のパターン追加
(2021 年 4 月 29 日)情報ウィンドウのオフセット位置を指定できるようにした.
(2021 年 4 月 17 日)27 件目以降の情報を表示しない不具合を修正.
(2021 年 4 月 8 日)キャッシュ・システム導入:pahooCache クラス.
(2021 年 3 月 27 日)求めたい震源の数を変更できるようにした。プログラム・タイトル変更。
(2021 年 3 月 3 日)震源の深さが「ごく浅い」ときにエラーが出る不具合を修正した。
(2021 年 2 月 27 日)気象庁サイト・リニューアルに対応。気象庁防災情報 XML から情報取得するよう変更した。

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

PHPで直近の地震情報を表示する
国土地理院地図表示

目次

サンプル・プログラム

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

プログラムの方針

気象庁の地震情報(https://www.jma.go.jp/jp/quake/)では、直近の地震情報を表示している。
正規表現を使って、このページから
  1. 発生日時分
  2. 震源地
  3. 震源の位置
  4. 震源の深さ
  5. 規模
  6. 最大震度
の 6 つを取り出していたが、2021 年(令和 3 年)2 月 24 日のサイト・リニューアルにより、スクレイピングによる取り出しが難しくなった。代わりに、気象庁防災情報 XML フォーマットから直近の地震情報を取り出し、これまで同様、震源の位置をマップ上にプロットすることにする。

気象庁防災情報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


震源・震度に関する情報は、長期フィードの地震火山の中にある電文コード VXSE53 に入っている。そこで、フィードから配信日時 yyyymmddhhmmss が最も大きく、VXSE53 を含む URL を取り出せば、それが目指す情報 XML となる。

VXSE53の構造

震源・震度に関する情報 XML VXSE53 の構造は下記の通りである。
ここから必要な情報を取り出す。
VXSE53の構造(xml) Report Control Title 震源・震度に関する情報 DateTime 作成日時 Status ステータス EditorialOffice 編集者 PublishingOffice 配信者 Head Title 震源・震度情報 ReportDateTime 報告日時 TargetDateTime 発生日時 EventID イベントID InfoType 情報の種類 Serial 連番 InfoKind 情報の種類 InfoKindVersion 情報バージョン Headline Text 説明文 Body Earthquake OriginTime 発生日時 ArrivalTime 到達日時 Hypocenter Area Name 震源域 Code 震央地名 Intensity Observation CodeDefine MaxInt 最大震度 Pref Name 都道府県名 Code 都道府県コード MaxInt 最大震度 Area Name 地域名 Code 地域コード MaxInt 最大震度 City Name 市区町村名 Code 市区町村コード MaxInt 最大震度 IntensityStation Name 区長村名(1) Code 区長村コード(1) Int 震度(2) IntensityStation Name 区長村名(2) Code 区長村コード(2) Int 震度(2)

準備: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 の登録方法」を参照されたい。

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

0044: //地図描画サービスの選択
0045: //    0:Google
0046: //    2:地理院地図・OSM
0047: define('MAPSERVICE', 2);

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

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

0067: //キャッシュ保持時間(分) 0:キャッシュしない
0068: //気象庁へのアクセス負荷軽減のため,適切な時間を設定してください.
0069: define('LIFE_CACHE_FEED',     5);   //高頻度 - 地震火山フィードに対して
0070: define('LIFE_CACHE_FEED_L', 120);   //長期 - 地震火山フィードに対して
0071: define('LIFE_CACHE_DATA',   720);   //地震情報に対して
0072: 
0073: //キャッシュ・ディレクトリ
0074: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
0075: //天気予報系のプログラムとは別のディレクトリを指定してください.
0076: define('DIR_CACHE_FEED',   './pcache1/');
0077: define('DIR_CACHE_FEED_L', './pcache2/');
0078: define('DIR_CACHE_DATA',   './pcache3/');

気象庁サイトへ負荷をかけないよう、キャッシュ・クラス pahooCache を導入した。使用方法については、「PHP で天気予報を求める - キャッシュ・システム」を参照いただきたい。
キャッシュ保持時間、キャッシュ・ディレクトリともに、自サイトの環境に応じて変更してほしい。天気予報系プログラムと別のキャッシュ・ディレクトリにした方が、お互いのキャッシュ保持時間の干渉を受けなくなる。
配布ファイルは、新しい地震情報が入ってくることを考え、フィードの方を短く、地震情報の方は長くキャッシュ保持時間を設定してある。

準備:各種定数

0049: //マップの表示サイズ(単位:ピクセル)
0050: define('MAP_WIDTH',  600);
0051: define('MAP_HEIGHT', 400);
0052: //マップID
0053: define('MAPID', 'map_id');
0054: //初期値
0055: define('DEF_TYPE', 'HYBRID');              //マップタイプ
0056: define('DEF_ZOOM',      6);                 //ズーム
0057: define('INFO_WIDTH', (MAP_WIDTH * 0.75));    //情報ウィンドウの最大幅
0058: define('INFO_OFFSET_X', +20);            //情報ウィンドウのオフセット位置(X)
0059: define('INFO_OFFSET_Y', -20);            //情報ウィンドウのオフセット位置(Y)
0060: 
0061: //Spinner - jQuery UI を使用するかどうか
0062: define('USESPINNER', TRUE);
0063: 
0064: define('DEF_NUMBER', 1);    //求めたい震源の数(初期値)
0065: define('MAX_NUMBER', 50);   //求めたい震源の数(最大)

表示に関わる各種パラメータは定数を define している。【変更不可】の記載のないものは、適宜変更してかまわない。

解説:気象庁防災情報XMLから最新の震源・震度に関する情報URLを取得

0379: /**
0380:  * 気象庁防災情報XMLから震源・震度に関する情報URLを取得
0381:  * @param   int    $mode    0:最新1件のみ取得,1:すべての情報URL取得
0382:  * @param   array  $urls    URL格納配列
0383:  * @param   string $errmsg  エラーメッセージ格納用
0384:  * @return  bool TRUE:取得成功/FALSE:取得失敗
0385: */
0386: function jma_getLastEarthquakeURLs($mode, &$urls, &$errmsg) {
0387:     //URLパターン
0388:     $vxse53 = '/http\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VXSE53\_[0-9]+\.xml/ui';
0389: 
0390:     //随時フィードの解析
0391:     $cnt = 0;
0392:     $pcc = new pahooCache(LIFE_CACHE_FEEDDIR_CACHE_FEED);
0393:     $xml = $pcc->simplexml_load(FEED);
0394:     //レスポンス・チェック
0395:     if ($pcc->iserror() || !isset($xml->entry)) {
0396:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0397:         return FALSE;
0398:     }
0399:     foreach ($xml->entry as $node) {
0400:         //URLを取得
0401:         if (preg_match($vxse53$node->id$arr) > 0) {
0402:             $urls[$cnt] = $arr[0];
0403:             $cnt++;
0404:         }
0405:     }
0406:     $pcc = NULL;
0407: 
0408:     //長期フィードの解析
0409:     $pcc = new pahooCache(LIFE_CACHE_FEED_LDIR_CACHE_FEED_L);
0410:     $xml = $pcc->simplexml_load(FEED_L);
0411:     //レスポンス・チェック
0412:     if ($pcc->iserror() || !isset($xml->entry)) {
0413:         $errmsg = '気象庁防災情報XMLにアクセスできません';
0414:         return FALSE;
0415:     }
0416:     foreach ($xml->entry as $node) {
0417:         //URLを取得
0418:         if (preg_match($vxse53$node->id$arr) > 0) {
0419:             if (array_search($arr[0]$urls) === FALSE) {
0420:                 $urls[$cnt] = $arr[0];
0421:                 $cnt++;
0422:             }
0423:         }
0424:     }
0425:     $pcc = NULL;
0426: 
0427:     //エラー・チェック
0428:     if ($cnt == 0) {
0429:         $errmsg = '直近の地震情報はありません';
0430:         return FALSE;
0431:     }
0432: 
0433:     //URLを日時の新しい順にソート
0434:     rsort($urls);
0435: 
0436:     return TRUE;
0437: }

前述のフィードから、最新の震源・震度に関する情報 URL を取得するユーザー関数が jma_getLastEarthquakeURLs である。
VXSE53 を含む URL を配列 $urls に格納していき、最後に配列 $urls を大きい順にソートすることで、ひづけんの新しい順にソートしたことになる。

解説:地震情報の取り出し

0439: /**
0440:  * 地震情報取得(気象庁防災情報XMLから)
0441:  * @param   object $pgc     pahooGeoCodeオブジェクト
0442:  * @param   array  $items   地震情報を格納する配列
0443:  * @param   string $urls    情報XMLのURLを格納する配列
0444:  * @param   string $errmsg  エラーメッセージ格納用
0445:  * @param   int    $count   取得件数(省略時=1)
0446:  * @return  bool TRUE:取得成功/FALSE:失敗
0447: */
0448: function get_earthquake($pgc, &$items, &$urls, &$errmsg$count=1) {
0449:     //名前空間
0450:     define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
0451:     //マッチングパターン
0452:     $pat1 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:([0-9]+)/ui';      //年月日時分
0453:     $pat2 = '/([\+\-][0-9\.]+)([\+\-][0-9\.]+)([\-\+\/])([0-9]*)/ui';   //緯度・経度・深さ
0454: 
0455:     //オブジェクト生成
0456:     $pcc = new pahooCache(LIFE_CACHE_DATADIR_CACHE_DATA);
0457: 
0458:     //最新の震源・震度に関する情報URLを取得
0459:     $urls = array();
0460:     $mode = ($count == 1) ? 0 : 1;
0461:     jma_getLastEarthquakeURLs($mode$urls$errmsg);
0462:     if ($errmsg != '')      return FALSE;
0463: 
0464:     foreach ($urls as $key=>$vxse53) {
0465:         //取得件数が上限だったらループ脱出
0466:         if ($key >= $count)     break;
0467: 
0468:         //震源情報の取得
0469:         $xml = $pcc->simplexml_load($vxse53);
0470:         //レスポンス・チェック
0471:         if ($pcc->iserror() || !isset($xml->Body->Earthquake)) {
0472:             $errmsg = '気象庁防災情報XMLから最新の震源・震度情報を取得できません';
0473:             return FALSE;
0474:         }
0475: 
0476:         //地震発生日時の取得
0477:         if ((preg_match($pat1, (string)$xml->Body->Earthquake->OriginTime$arr) > 0) && isset($arr[5])) {
0478:             $items[$key]['year']    = (int)$arr[1];
0479:             $items[$key]['month']   = (int)$arr[2];
0480:             $items[$key]['day']     = (int)$arr[3];
0481:             $items[$key]['hour']    = (int)$arr[4];
0482:             $items[$key]['minuite'] = (int)$arr[5];
0483:         } else {
0484:             $errmsg = '気象庁防災情報XMLから最新の震源・震度情報を取得できません';
0485:             return FALSE;
0486:         }
0487: 
0488:         //震源地の取得
0489:         if (isset($xml->Body->Earthquake->Hypocenter->Area->Name)) {
0490:             $items[$key]['location'] = $xml->Body->Earthquake->Hypocenter->Area->Name;
0491:         } else {
0492:             $items[$key]['location'] = '不明';
0493:         }
0494:         if ($items[$key]['location'] == '') {
0495:             $items[$key]['location'] = '不明';
0496:         }
0497:         $node = $xml->Body->Earthquake->Hypocenter->Area->children(JMX_EB);
0498:         if (preg_match($pat2, (string)$node->Coordinate$arr)) {
0499:             if (isset($arr[1]) && isset($arr[2])) {
0500:                 list($items[$key]['longitude'], $items[$key]['latitude']) = $pgc->tokyo_wgs84((float)$arr[2], (float)$arr[1]);
0501:             } else {
0502:                 $items[$key]['latitude'] = $items[$key]['longitude'] = '不明';
0503:             }
0504:             if (isset($arr[3]) && ($arr[3] == '/')) {
0505:                 $items[$key]['depth']  = '不明';
0506:             } else if (isset($arr[4])) {
0507:                 $items[$key]['depth']  = (float)$arr[4];
0508:             } else {
0509:                 $items[$key]['depth']  = '不明';
0510:             }
0511:         }
0512: 
0513:         //マグニチュードの取得
0514:         $node = $xml->Body->Earthquake->children(JMX_EB);
0515:         if (isset($node->Magnitude)) {
0516:             $items[$key]['magnitude']  = (float)$node->Magnitude;
0517:         } else {
0518:             $items[$key]['magnitude']  = '不明';
0519:         }
0520: 
0521:         //震度を取得
0522:         if (isset($xml->Body->Intensity->Observation->MaxInt)) {
0523:             $items[$key]['maxintensity'] = (int)$xml->Body->Intensity->Observation->MaxInt;
0524:         } else {
0525:             $items[$key]['maxintensity'] = '不明';
0526:         }
0527:     }
0528: 
0529:     //オブジェクト解法
0530:     $pcc = NULL;
0531: 
0532:     return TRUE;
0533: }

震源・震度に関する情報 XML VXSE53 から、地震発生年月日、震源の緯度・経度・深さ、地震の規模を表すマグニチュード、最大震度を取り出し、配列 $items に格納する。

0574: /**
0575:  * 日本測地系を世界測地系に変換する
0576:  * @param   float $long 経度(日本測地系)
0577:  * @param   float $lat  緯度(日本測地系)
0578:  * @return  float array(経度,緯度)(世界測地系)
0579: */
0580: function tokyo_wgs84($long$lat) {
0581:     $glong = $long - $lat * 0.000046038 - $long * 0.000083043 + 0.010040;
0582:     $glat  = $lat  - $lat * 0.00010695  + $long * 0.000017464 + 0.0046017;
0583:     return array($glong$glat);
0584: }

ここで、緯度・経度は日本測地系であるため、あとでマップ描画しやすいように、pahooGeoCode クラスのメソッド tokyo_wgs84 を使って世界測地系に変換しておく。

解説:地震情報をマッピング情報に変換

0546: /**
0547:  * 地震情報をマッピング情報に変換
0548:  * @param   array  $items   地震情報を格納した配列
0549:  * @param   array  $points  マッピング情報を格納する配列
0550:  * @return  int 変換したマッピング情報の件数
0551: */
0552: function info2points(&$items, &$points) {
0553:     $i = 0;
0554:     $j = 0;
0555:     foreach ($items as $i=>$item) {
0556:         //同じマッピング位置があるかどうか
0557:         $flag = FALSE;
0558:         for ($k = 0; $k < $j$k++) {
0559:             if (($items[$i]['latitude'] == $points[$k + 1]['latitude']) && ($items[$i]['longitude'] == $points[$k + 1]['longitude'])) {
0560:                 $flag = TRUE;
0561:                 $points[$k + 1]['description'] .= '<br /><hr />';
0562:                 break;
0563:             }
0564:         }
0565:         //新規のマッピング位置
0566:         if (! $flag) {
0567:             $k = $j;
0568:             $points[$k + 1]['latitude']    = $items[$i]['latitude'];
0569:             $points[$k + 1]['longitude']   = $items[$i]['longitude'];
0570:             $points[$k + 1]['title'] = '';
0571:             $points[$k + 1]['description'] = '';
0572:             $j++;
0573:         }
0574:         $items[$i]['id'] = num2alpha($k + 1);
0575:         $dt = makeDateTime($items[$i]);
0576:         $latitude  = sprintf('%.1f', $items[$i]['latitude']);
0577:         $longitude = sprintf('%.1f', $items[$i]['longitude']);
0578:         if (is_numeric($items[$i]['magnitude'])) {
0579:             $magnitude = sprintf('%.1f', $items[$i]['magnitude']);
0580:         } else {
0581:             $magnitude = $items[$i]['magnitude'];
0582:         }
0583:         if (is_numeric($items[$i]['depth'])) {
0584:             if ($items[$i]['depth'] == 0) {
0585:                 $depth = 'ごく浅い';
0586:             } else {
0587:                 $depth = sprintf('約%dkm', $items[$i]['depth'] / 1000);
0588:             }
0589:         } else {
0590:             $depth = $items[$i]['depth'];
0591:         }
0592: $points[$k + 1]['description'] .=<<< EOT
0593: 発生日時:{$dt}<br />震源地:{$items[$i]['location']}<br />震源の位置:北緯 {$latitude}度,東経 {$longitude}度<br />震源の深さ:{$depth}<br />地震の規模:マグニチュード {$magnitude}<br />最大震度:{$items[$i]['maxintensity']}
0594: EOT;
0595:         //打ち切り条件
0596:         if ($j >= 26)   break;
0597:     }
0598: 
0599:     return $j;
0600: }

同じ震源で複数回地震が起きることがある。
そこで、ユーザー関数 info2points を使って、震源 1 つに対して 1 つの要素が対応する配列 [$items] に対し、同じ緯度・経度に対して 1 つの要素が対応する配列 [$points] に情報を変換してやる。
こうすることで、同一震源にマップ・アイコンを 1 つだけ立てて、情報ウィンドウに複数回の地震情報を羅列するようにできる。

解説:地図描画について

マップの描画については、「PHP で住所・ランドマークから最寄り駅を求める」をご覧いただきたい。

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

0866: //パラメータ
0867: $number = getParam('number', FALSEDEF_NUMBER);
0868: $number = trim($number);
0869: if (preg_match('/^[0-9]+$/i', $number) > 0) {
0870:     $errmsg = '';
0871:     if (($number < 1) || ($number > MAX_NUMBER)) {
0872:         $errmsg = sprintf('判定できる整数の範囲は,1から%dまでです', MAX_NUMBER);
0873:     }
0874: else {
0875:     $errmsg = '数値は正の整数(自然数)を指定してください';
0876: }

URL パラメータを使って

earthquake.php?number=10


のようにすることで、number で指定した値が求めたい震源の数になる。

解説:ツイート機能

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

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

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

解説:html2canvasライブラリ

0141: <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
0142: <script>
0143: function mytweet() {
0144:     $('#tweet').val('1');
0145:     document.myform.submit();
0146: }

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

0837: <div id="{$target}" name="{$target}" style="width:{$width2}px;">
0838: <p>
0839: ⚠️最近の地震情報 {$dt}現在
0840: </p>
0841: {$html}
0842: </div>

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

0317: /**
0318:  * HTMLオブジェクトの画像化
0319:  * @param   なし
0320:  * @return  string JavaScriptコード
0321: */
0322: function js_html2image() {
0323:     $target = TARGET;
0324:     $js = '';
0325: 
0326:     //Googleマップの場合
0327:     if (MAPSERVICE == 0) {
0328: $js .=<<< EOT
0329: google.maps.event.addListener(map, 'tilesloaded', function() {
0330:     var capture = document.querySelector('#{$target}');
0331:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
0332:         var base64 = canvas.toDataURL('image/png');       //画像化
0333:         $('#base64').val(base64);
0334:     });
0335: });
0336: 
0337: EOT;
0338: 
0339:     //Leafletの場合(ブラウザによってはうまく動作しない)
0340:     } else {
0341: $js .=<<< EOT
0342: HTMLCanvasElement.prototype.getContext = function(origFn) {
0343:     return function(type, attribs) {
0344:         attribs = attribs || {};
0345:         attribs.preserveDrawingBuffer = true;
0346:         return origFn.call(this, type, attribs);
0347:     };
0348: } (HTMLCanvasElement.prototype.getContext);
0349: 
0350: //HTML画像化イベント登録
0351: function html2image() {
0352:     var capture = document.querySelector('#{$target}');
0353:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
0354:         var base64 = canvas.toDataURL('image/png');       //画像化
0355:         $('#base64').val(base64);
0356:     });
0357: };
0358: 
0359: //ズーム変更イベント
0360: map.on('zoomend', function() {
0361:     html2image();
0362: });
0363: 
0364: //マップ移動イベント
0365: map.on('moveend', function() {
0366:     html2image();
0367: });
0368: 
0369: //ズーム変更イベント発生
0370: var zoom = map.getZoom();
0371: map.setZoom(zoom);
0372: 
0373: EOT;
0374:     }
0375: 
0376:     return $js;
0377: }

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 メソッドを用意した。

解説:ツイート処理

0292: /**
0293:  * ツイート処理
0294:  * @param   string $message 投稿文
0295:  * @param   string $res     応答メッセージ格納用
0296:  * @return  bool TRUE:成功/FALSE:失敗または未処理
0297: */
0298: function mediaTweet($message, &$res) {
0299:     if (! TWITTER)   return FALSE;
0300: 
0301:     $ret = TRUE;
0302:     if (isset($_POST['base64']) && ($_POST['base64'] != '')) {
0303:         $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
0304:         $raws = array(base64_decode($base64));
0305:         $ptw = new pahooTwitterAPI();
0306:         $ptw->tweet_media_raw($message$raws);
0307:         $errmsg = $ptw->errmsg;
0308:         $ret = ! $ptw->error;
0309:         $ptw = NULL;
0310:         if ($ret) {
0311:             $res = 'ツイートしました';
0312:         }
0313:     }
0314:     return $ret;
0315: }

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

0889: //ツイート機能
0890: if (getParam('tweet', FALSE, 0) > 0) {
0891:     $dt = nowDT();
0892: $message =<<< EOT
0893: ⚠️最近の地震情報 {$dt}現在
0894: 
0895: (ご参考)PHPで最近の地震情報を表示する https://www.pahoo.org/e-soul/webtech/php05/php05-17-01.shtm #地震
0896: 
0897: EOT;
0898:     mediaTweet($message$res);

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

活用例

みんなの知識 ちょっと便利帳」では、「日本の直近の地震情報」で本プログラムを利用し、見やすいページを提供している。ありがとうございます。

参考サイト

(この項おわり)
header