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

目次
- サンプル・プログラムの実行例
- サンプル・プログラム
- プログラムの方針
- 気象庁防災情報XMLフォーマット
- VXSE53の構造
- 準備:pahooGeoCode クラス
- 準備:地図サービスの選択
- 準備:キャッシュ・システム
- 準備:各種定数
- 解説:気象庁防災情報XMLから最新の震源・震度に関する情報URLを取得
- 解説:地震情報の取り出し
- 解説:地震情報をマッピング情報に変換
- 解説:地図描画について
- 解説:オーバーレイ表示(Leaflet選択時のみ)
- 解説:表示とURLパラメータ
- 解説:ツイート機能
- 解説:html2canvasライブラリ
- 準備:pahooTwitterAPI クラス
- 解説:メディア付き投稿(RAWデータ)
- 解説:ツイート処理
- 活用例
- 参考サイト
サンプル・プログラム
earthquake.php | サンプル・プログラム本体。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
pahooCache.php | キャッシュ処理に関わるクラス pahooCache。 キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。 |
pahooTwitterAPI.php | Twitter APIを利用するクラス pahooTwitterAPI。 使い方は「PHPでTwitterに投稿(ツイート)する」などを参照。include_path が通ったディレクトリに配置すること。 |
pahooBlueskyAPI.php | Bluesky APIに関わるクラス pahooBlueskyAPI。 使い方は「PHPでPHPでBlueskyに投稿する」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
5.6.0 | 2024/12/08 | Bluesky投稿機能を追加, isButton()修正 |
5.5.0 | 2024/06/22 | Twitter(現・X)ボタンを "X" に変更 |
5.4.1 | 2023/09/20 | js_html2image()--Leaflet用html2image()発火プロセス見直し |
5.4 | 2022/03/19 | 気象庁防災情報XMLのhttps化に対応 |
5.3 | 2021/12/03 | 色別標高図+活断層図をオーバーレイ表示 |
バージョン | 更新日 | 内容 |
---|---|---|
6.3.3 | 2024/09/14 | $this->NOMINATIM_EMAIL 追加 |
6.3.2 | 2024/02/14 | getStaticMap() -- bug-fix |
6.3.1 | 2023/07/09 | bug-fix |
6.3.0 | 2023/07/02 | getPointsGSI()追加 |
6.2.0 | 2023/07/02 | ip2address()追加 |
バージョン | 更新日 | 内容 |
---|---|---|
1.1.1 | 2023/02/11 | コメント追記 |
1.1 | 2021/04/08 | simplexml_load()メソッド追加 |
1.0 | 2021/04/02 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
5.5.1 | 2024/11/23 | __construct() -- PHP8.4における応急処置 |
5.5.0 | 2024/06/21 | TwitterOAuth 7.0.0 対応 |
5.4.0 | 2024/05/18 | twitter.com → x.com 変更対応 |
5.3.0 | 2023/08/15 | tweet3() -- メディアのシャフル機能 |
5.2.1 | 2023/07/22 | bug-fix |
バージョン | 更新日 | 内容 |
---|---|---|
1.8.0 | 2024/12/06 | post()--引用時にも画像やOGP情報を付けられるように |
1.7.0 | 2024/12/06 | reductImage()--OGPかどうかに関わらず画像が指定サイズに収めるよう拡大・縮小する仕様に変更 |
1.6.1 | 2024/12/04 | getRootParentID() -- API仕様変更対応 |
1.6.0 | 2024/11/30 | convertTransparentToWhite() 追加 |
1.5.0 | 2024/11/30 | reductImage() -- 指定幅より小さい場合を追加;reductImage, uploadBlobの引数に$flagCard追加 |
プログラムの方針
正規表現を使って、このページから
- 発生日時分
- 震源地
- 震源の位置
- 震源の深さ
- 規模
- 最大震度
気象庁防災情報XMLフォーマット
https://www.data.jma.go.jp/developer/xml/data/yyyymmddhhmmss_番号_電文コード_連番.xml震源・震度に関する情報は、長期フィードの地震火山の中にある電文コード VXSE53 に入っている。そこで、フィードから配信日時 yyyymmddhhmmss が最も大きく、VXSE53 を含むURLを取り出せば、それが目指す情報XMLとなる。
VXSE53の構造
ここから必要な情報を取り出す。
準備:pahooGeoCode クラス
pahooGeoCode.php
37: class pahooGeoCode {
38: var $items; //検索結果格納用
39: var $error; //エラー・フラグ
40: var $errmsg; //エラー・メッセージ
41: var $hits; //検索ヒット件数
42: var $webapi; //直前に呼び出したWebAPI URL
43:
44: //Google Cloud Platform APIキー
45: //https://cloud.google.com/maps-platform/
46: //※Google Maps APIを利用しないのなら登録不要
47: var $GOOGLE_API_KEY_1 = '**************************'; //HTTPリファラ用
48: var $GOOGLE_API_KEY_2 = '**************************'; //IP制限用
49:
50: //Yahoo! JAPAN Webサービス アプリケーションID
51: //https://e.developer.yahoo.co.jp/register
52: //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
53: var $YAHOO_APPLICATION_ID = '*****************************';
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図としてGoogleマップを利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を参照されたい。
準備:地図サービスの選択
earthquake.php
47: // 地図描画サービスの選択
48: // 0:Google
49: // 2:地理院地図・OSM
50: define('MAPSERVICE', 2);

準備:キャッシュ・システム
earthquake.php
71: // キャッシュ保持時間(分) 0:キャッシュしない
72: // 気象庁へのアクセス負荷軽減のため,適切な時間を設定してください.
73: define('LIFE_CACHE_FEED', 5); // 高頻度 - 地震火山フィードに対して
74: define('LIFE_CACHE_FEED_L', 120); // 長期 - 地震火山フィードに対して
75: define('LIFE_CACHE_DATA', 720); // 地震情報に対して
76:
77: // キャッシュ・ディレクトリ
78: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
79: // 天気予報系のプログラムとは別のディレクトリを指定してください.
80: define('DIR_CACHE_FEED', './pcache1/');
81: define('DIR_CACHE_FEED_L', './pcache2/');
82: define('DIR_CACHE_DATA', './pcache3/');
キャッシュ保持時間、キャッシュ・ディレクトリともに、自サイトの環境に応じて変更してほしい。天気予報系プログラムと別のキャッシュ・ディレクトリにした方が、お互いのキャッシュ保持時間の干渉を受けなくなる。
配布ファイルは、新しい地震情報が入ってくることを考え、フィードの方を短く、地震情報の方は長くキャッシュ保持時間を設定してある。
準備:各種定数など
earthquake.php
37: // 各種定数(START) ===========================================================
38: // Twitter(現・X)投稿ボタン TRUE:有効,FALSE:無効
39: define('TWITTER', FALSE);
40:
41: // Bluesky投稿ボタン TRUE:有効,FALSE:無効
42: define('BLUESKY', FALSE);
43:
44: // 画像化したいオブジェクト
45: define('TARGET', 'target');
46:
47: // 地図描画サービスの選択
48: // 0:Google
49: // 2:地理院地図・OSM
50: define('MAPSERVICE', 2);
51:
52: // マップの表示サイズ(単位:ピクセル)
53: define('MAP_WIDTH', 600);
54: define('MAP_HEIGHT', 400);
55: // マップID
56: define('MAPID', 'map_id');
57: // 初期値
58: define('DEF_ZOOM', 6); // ズーム
59: define('DEF_TYPE', 'ROADMAP'); // マップタイプ
60: define('DEF_OVERLAYS', 'GSIELEV,GSIFAULT'); // オーバーレイ(Leaflet使用時のみ)
61: define('INFO_WIDTH', (MAP_WIDTH * 0.75)); // 情報ウィンドウの最大幅
62: define('INFO_OFFSET_X', +20); // 情報ウィンドウのオフセット位置(X)
63: define('INFO_OFFSET_Y', -20); // 情報ウィンドウのオフセット位置(Y)
64:
65: // Spinner - jQuery UI を使用するかどうか
66: define('USESPINNER', TRUE);
67:
68: define('DEF_NUMBER', 1); // 求めたい震源の数(初期値)
69: define('MAX_NUMBER', 50); // 求めたい震源の数(最大)
70:
71: // キャッシュ保持時間(分) 0:キャッシュしない
72: // 気象庁へのアクセス負荷軽減のため,適切な時間を設定してください.
73: define('LIFE_CACHE_FEED', 5); // 高頻度 - 地震火山フィードに対して
74: define('LIFE_CACHE_FEED_L', 120); // 長期 - 地震火山フィードに対して
75: define('LIFE_CACHE_DATA', 720); // 地震情報に対して
76:
77: // キャッシュ・ディレクトリ
78: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
79: // 天気予報系のプログラムとは別のディレクトリを指定してください.
80: define('DIR_CACHE_FEED', './pcache1/');
81: define('DIR_CACHE_FEED_L', './pcache2/');
82: define('DIR_CACHE_DATA', './pcache3/');
83:
84: // 気象庁防災情報XML:高頻度フィード - 地震火山【変更不可】
85: define('FEED', 'https://www.data.jma.go.jp/developer/xml/feed/eqvol.xml');
86:
87: // 気象庁防災情報XML:長期フィード - 地震火山【変更不可】
88: define('FEED_L', 'https://www.data.jma.go.jp/developer/xml/feed/eqvol_l.xml');
89:
90: // 住所・緯度・経度に関わるクラス:include_pathが通ったディレクトリに配置
91: require_once('pahooGeoCode.php');
92:
93: // キャッシュ処理に関わるクラス:include_pathが通ったディレクトリに配置
94: require_once('pahooCache.php');
95:
96: // Twitterクラス:include_pathが通ったディレクトリに配置
97: if (TWITTER) {
98: require_once('pahooTwitterAPI.php');
99: }
100:
101: // BlueskyAPIクラス:include_pathが通ったディレクトリに配置
102: if (BLUESKY) {
103: require_once('pahooBlueskyAPI.php');
104: define('BLUESKY_DOMAIN', 'bsky.social'); // あなたのドメインを記入
105: }
106: // 各種定数(END) ===============================================================

出力結果を Twitter(現・X) に投稿することができる。投稿機能を有効化するときは、定数 TWITTER を TRUE にする。ユーザー定義クラス pahooTwitterAPI を利用するので、"pahooTwitterAPI.php" をinclude_pathが通ったディレクトリに配置すること。また、事前にアプリケーションの登録が必要になる。詳しくは「PHPでTwitter(現・X)に投稿(ツイート)する」を参照してほしい。

出力結果を Bluesky に投稿することができる。投稿機能を有効化するときは、定数 BLUESKY を TRUE にする。ユーザー定義クラス pahooBlueskyAPI を利用するので、"pahooBlueskyAPI.php" をinclude_pathが通ったディレクトリに配置すること。また、事前にアプリケーションの登録が必要になる。詳しくは「PHPでPHPでBlueskyに投稿する」を参照してほしい。

Twitter(現・X) や Bluesky のボタン・アイコンについては、「HTMLとCSSでさまざまなアイコンを表示する」を参照して欲しい。
解説:気象庁防災情報XMLから最新の震源・震度に関する情報URLを取得
earthquake.php
433: /**
434: * 気象庁防災情報XMLから震源・震度に関する情報URLを取得
435: * @param int $mode 0:最新1件のみ取得,1:すべての情報URL取得
436: * @param array $urls URL格納配列
437: * @param string $errmsg エラーメッセージ格納用
438: * @return bool TRUE:取得成功/FALSE:取得失敗
439: */
440: function jma_getLastEarthquakeURLs($mode, &$urls, &$errmsg) {
441: // URLパターン
442: $vxse53 = '/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VXSE53\_[0-9]+\.xml/ui';
443:
444: // 随時フィードの解析
445: $cnt = 0;
446: $pcc = new pahooCache(LIFE_CACHE_FEED, DIR_CACHE_FEED);
447: $xml = $pcc->simplexml_load(FEED);
448: // レスポンス・チェック
449: if ($pcc->iserror() || !isset($xml->entry)) {
450: $errmsg = '気象庁防災情報XMLにアクセスできません';
451: return FALSE;
452: }
453: foreach ($xml->entry as $node) {
454: // URLを取得
455: if (preg_match($vxse53, $node->id, $arr) > 0) {
456: $urls[$cnt] = $arr[0];
457: $cnt++;
458: }
459: }
460: $pcc = NULL;
461:
462: // 長期フィードの解析
463: $pcc = new pahooCache(LIFE_CACHE_FEED_L, DIR_CACHE_FEED_L);
464: $xml = $pcc->simplexml_load(FEED_L);
465: // レスポンス・チェック
466: if ($pcc->iserror() || !isset($xml->entry)) {
467: $errmsg = '気象庁防災情報XMLにアクセスできません';
468: return FALSE;
469: }
470: foreach ($xml->entry as $node) {
471: // URLを取得
472: if (preg_match($vxse53, $node->id, $arr) > 0) {
473: if (array_search($arr[0], $urls) === FALSE) {
474: $urls[$cnt] = $arr[0];
475: $cnt++;
476: }
477: }
478: }
479: $pcc = NULL;
480:
481: // エラー・チェック
482: if ($cnt == 0) {
483: $errmsg = '直近の地震情報はありません';
484: return FALSE;
485: }
486:
487: // URLを日時の新しい順にソート
488: rsort($urls);
489:
490: return TRUE;
491: }
VXSE53 を含むURLを配列 $urls に格納していき、最後に配列 $urls を大きい順にソートすることで、ひづけんの新しい順にソートしたことになる。
解説:地震情報の取り出し
earthquake.php
493: /**
494: * 地震情報取得(気象庁防災情報XMLから)
495: * @param object $pgc pahooGeoCodeオブジェクト
496: * @param array $items 地震情報を格納する配列
497: * @param string $urls 情報XMLのURLを格納する配列
498: * @param string $errmsg エラーメッセージ格納用
499: * @param int $count 取得件数(省略時=1)
500: * @return bool TRUE:取得成功/FALSE:失敗
501: */
502: function get_earthquake($pgc, &$items, &$urls, &$errmsg, $count=1) {
503: // 名前空間
504: define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
505: // マッチングパターン
506: $pat1 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:([0-9]+)/ui'; // 年月日時分
507: $pat2 = '/([\+\-][0-9\.]+)([\+\-][0-9\.]+)([\-\+\/])([0-9]*)/ui'; // 緯度・経度・深さ
508:
509: // オブジェクト生成
510: $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA);
511:
512: // 最新の震源・震度に関する情報URLを取得
513: $urls = array();
514: $mode = ($count == 1) ? 0 : 1;
515: jma_getLastEarthquakeURLs($mode, $urls, $errmsg);
516: if ($errmsg != '') return FALSE;
517:
518: foreach ($urls as $key=>$vxse53) {
519: // 取得件数が上限だったらループ脱出
520: if ($key >= $count) break;
521:
522: // 震源情報の取得
523: $xml = $pcc->simplexml_load($vxse53);
524: // レスポンス・チェック
525: if ($pcc->iserror() || !isset($xml->Body->Earthquake)) {
526: $errmsg = '気象庁防災情報XMLから最新の震源・震度情報を取得できません';
527: return FALSE;
528: }
529:
530: // 地震発生日時の取得
531: if ((preg_match($pat1, (string)$xml->Body->Earthquake->OriginTime, $arr) > 0) && isset($arr[5])) {
532: $items[$key]['year'] = (int)$arr[1];
533: $items[$key]['month'] = (int)$arr[2];
534: $items[$key]['day'] = (int)$arr[3];
535: $items[$key]['hour'] = (int)$arr[4];
536: $items[$key]['minuite'] = (int)$arr[5];
537: } else {
538: $errmsg = '気象庁防災情報XMLから最新の震源・震度情報を取得できません';
539: return FALSE;
540: }
541:
542: // 震源地の取得
543: if (isset($xml->Body->Earthquake->Hypocenter->Area->Name)) {
544: $items[$key]['location'] = $xml->Body->Earthquake->Hypocenter->Area->Name;
545: } else {
546: $items[$key]['location'] = '不明';
547: }
548: if ($items[$key]['location'] == '') {
549: $items[$key]['location'] = '不明';
550: }
551: $node = $xml->Body->Earthquake->Hypocenter->Area->children(JMX_EB);
552: if (preg_match($pat2, (string)$node->Coordinate, $arr)) {
553: if (isset($arr[1]) && isset($arr[2])) {
554: list($items[$key]['longitude'], $items[$key]['latitude']) = $pgc->tokyo_wgs84((float)$arr[2], (float)$arr[1]);
555: } else {
556: $items[$key]['latitude'] = $items[$key]['longitude'] = '不明';
557: }
558: if (isset($arr[3]) && ($arr[3] == '/')) {
559: $items[$key]['depth'] = '不明';
560: } else if (isset($arr[4])) {
561: $items[$key]['depth'] = (float)$arr[4];
562: } else {
563: $items[$key]['depth'] = '不明';
564: }
565: }
566:
567: // マグニチュードの取得
568: $node = $xml->Body->Earthquake->children(JMX_EB);
569: if (isset($node->Magnitude)) {
570: $items[$key]['magnitude'] = (float)$node->Magnitude;
571: } else {
572: $items[$key]['magnitude'] = '不明';
573: }
574:
575: // 震度を取得
576: if (isset($xml->Body->Intensity->Observation->MaxInt)) {
577: $items[$key]['maxintensity'] = (int)$xml->Body->Intensity->Observation->MaxInt;
578: } else {
579: $items[$key]['maxintensity'] = '不明';
580: }
581: }
582:
583: // オブジェクト解法
584: $pcc = NULL;
585:
586: return TRUE;
587: }
pahooGeoCode.php
587: /**
588: * 日本測地系を世界測地系に変換する
589: * @param float $long 経度(日本測地系)
590: * @param float $lat 緯度(日本測地系)
591: * @return float array(経度,緯度)(世界測地系)
592: */
593: function tokyo_wgs84($long, $lat) {
594: $glong = $long - $lat * 0.000046038 - $long * 0.000083043 + 0.010040;
595: $glat = $lat - $lat * 0.00010695 + $long * 0.000017464 + 0.0046017;
596: return array($glong, $glat);
597: }
解説:地震情報をマッピング情報に変換
earthquake.php
600: /**
601: * 地震情報をマッピング情報に変換
602: * @param array $items 地震情報を格納した配列
603: * @param array $points マッピング情報を格納する配列
604: * @return int 変換したマッピング情報の件数
605: */
606: function info2points(&$items, &$points) {
607: $i = 0;
608: $j = 0;
609: foreach ($items as $i=>$item) {
610: // 同じマッピング位置があるかどうか
611: $flag = FALSE;
612: for ($k = 0; $k < $j; $k++) {
613: if (($items[$i]['latitude'] == $points[$k + 1]['latitude']) && ($items[$i]['longitude'] == $points[$k + 1]['longitude'])) {
614: $flag = TRUE;
615: $points[$k + 1]['description'] .= '<br /><hr />';
616: break;
617: }
618: }
619: // 新規のマッピング位置
620: if (! $flag) {
621: $k = $j;
622: $points[$k + 1]['latitude'] = $items[$i]['latitude'];
623: $points[$k + 1]['longitude'] = $items[$i]['longitude'];
624: $points[$k + 1]['title'] = '';
625: $points[$k + 1]['description'] = '';
626: $j++;
627: }
628: $items[$i]['id'] = num2alpha($k + 1);
629: $dt = makeDateTime($items[$i]);
630: $latitude = sprintf('%.1f', $items[$i]['latitude']);
631: $longitude = sprintf('%.1f', $items[$i]['longitude']);
632: if (is_numeric($items[$i]['magnitude'])) {
633: $magnitude = sprintf('%.1f', $items[$i]['magnitude']);
634: } else {
635: $magnitude = $items[$i]['magnitude'];
636: }
637: if (is_numeric($items[$i]['depth'])) {
638: if ($items[$i]['depth'] == 0) {
639: $depth = 'ごく浅い';
640: } else {
641: $depth = sprintf('約%dkm', $items[$i]['depth'] / 1000);
642: }
643: } else {
644: $depth = $items[$i]['depth'];
645: }
646: $points[$k + 1]['description'] .=<<< EOT
647: 発生日時:{$dt}<br />震源地:{$items[$i]['location']}<br />震源の位置:北緯 {$latitude}度,東経 {$longitude}度<br />震源の深さ:{$depth}<br />地震の規模:マグニチュード {$magnitude}<br />最大震度:{$items[$i]['maxintensity']}
648: EOT;
649: // 打ち切り条件
650: if ($j >= 26) break;
651: }
652:
653: return $j;
654: }
そこで、ユーザー関数 info2points を使って、震源1つに対して1つの要素が対応する配列 [$items] に対し、同じ緯度・経度に対して1つの要素が対応する配列 [$points] に情報を変換してやる。
こうすることで、同一震源にマップ・アイコンを1つだけ立てて、情報ウィンドウに複数回の地震情報を羅列するようにできる。
解説:地図描画について
解説:オーバーレイ表示(Leaflet選択時のみ)
オーバーレイ地図としては、地理院地図の 色別標高図、活断層図(都市圏活断層図)、治水地形分類図 更新版(2007~2020年)の3つを用意した。
pahooGeoCode.php
1948: //地理院地図:色別標高図(オーバーレイ)
1949: let GSIELEV = new L.tileLayer(
1950: 'https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
1951: {
1952: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1953: opacity: 0.6,
1954: minZoom: 5,
1955: maxZoom: 15,
1956: name: 'GSIELEV'
1957: });
1958: //地理院地図:活断層図(オーバーレイ)
1959: let GSIFAULT = new L.tileLayer(
1960: 'https://cyberjapandata.gsi.go.jp/xyz/afm/{z}/{x}/{y}.png',
1961: {
1962: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1963: opacity: 0.6,
1964: minZoom: 11,
1965: maxZoom: 16,
1966: name: 'GSIFAULT'
1967: });
1968: //地理院地図:治水地形分類図 更新版(オーバーレイ)
1969: let GSIFLOOD = new L.tileLayer(
1970: 'https://cyberjapandata.gsi.go.jp/xyz/lcmfc2/{z}/{x}/{y}.png',
1971: {
1972: attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1973: opacity: 0.6,
1974: minZoom: 11,
1975: maxZoom: 16,
1976: name: 'GSIFLOOD'
1977: });
1978:
1979: //baseMapsオブジェクトにオーバーレイ設定
1980: let overlayMaps = {
1981: "色別標高図" : GSIELEV,
1982: "活断層図" : GSIFAULT,
1983: "治水地形分類図" : GSIFLOOD,
1984: };
1985:
1986: //layersコントロールにbaseMapsオブジェクトを設定して地図に追加
1987: L.control.layers(baseMaps, overlayMaps).addTo(map);
1988: {$type}.addTo(map);
1989: {$addoverlay}

解説:表示とURLパラメータ
earthquake.php
940: // パラメータ
941: $number = getParam('number', FALSE, DEF_NUMBER);
942: $number = trim($number);
943: if (preg_match('/^[0-9]+$/i', $number) > 0) {
944: $errmsg = '';
945: if (($number < 1) || ($number > MAX_NUMBER)) {
946: $errmsg = sprintf('判定できる整数の範囲は,1から%dまでです', MAX_NUMBER);
947: }
948: } else {
949: $errmsg = '数値は正の整数(自然数)を指定してください';
950: }
951: $zoom = getParam('zoom', FALSE, DEF_ZOOM);
952: $zoom = ($zoom == '') ? DEF_ZOOM : $zoom;
953: $type = getParam('type', FALSE, DEF_TYPE);
954: $type = ($type == '') ? DEF_TYPE : $type;
955: $overlays = getParam('overlays', FALSE, DEF_OVERLAYS);
956: $overlays = ($overlays == '') ? DEF_OVERLAYS : $overlays;
earthquake.php?number=10のようにすることで、number で指定した値が求めたい震源の数になる。
また、
earthquake.php?number=10&zoom=8&type=GSISTD&overlays=GSIELEV,GSIFAULTのように指定すると、震源数が10、拡大率が8、ベースマップが地理院地図、オーバーレイは色別標高図と活断層図で表示する。
解説:SNS投稿機能

コンテンツを描画しているのはクライアントにあるブラウザ(レンダリングエンジン)であることから、クライアント側で画像を作成し、サーバ側で SNS の API をコールするという方針とした。
- ブラウザはサーバにコンテンツ描画をリクエストする。
- サーバはデータサイトからデータ取得する。(サーバキャッシュにデータがあればそれを利用する)
- サーバはコンテンツ描画スクリプトを生成する。
- サーバはブラウザへレスポンス(HTML文)を返す。
- ブラウザはコンテンツをレンダリングする。
- ブラウザはレンダリングしたコンテンツを画像データとしてサーバへアップロードする。
- サーバは SNS の API を使ってツイートする。
- サーバは SNS へメッセージと画像を送る。
- サーバはブラウザへレスポンス(HTML文)を返す。
解説:html2canvasライブラリ
earthquake.php
152: <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
153: <script>
154: function mytweet() {
155: $('#tweet').val('1');
156: document.myform.submit();
157: }
画像化を実行するJavaScript関数は html2canvas である。
earthquake.php
910: <div id="{$target}" name="{$target}" style="width:{$width2}px;">
911: <p>
912: ⚠️最近の地震情報 {$dt}現在
913: </p>
914: {$html}
915: </div>
レンダリングエンジンによって違うのかもしれないが、html2canvas ライブラリによって画像化される範囲が実際よりやや小さいため、あえてマップ領域より、幅を20ピクセル大きくした範囲を画像化範囲としている。
earthquake.php
368: /**
369: * HTMLオブジェクトの画像化
370: * @param なし
371: * @return string JavaScriptコード
372: */
373: function js_html2image() {
374: $target = TARGET;
375: $js = '';
376:
377: // Googleマップの場合
378: if (MAPSERVICE == 0) {
379: $js .=<<< EOT
380: google.maps.event.addListener(map, 'tilesloaded', function() {
381: var capture = document.querySelector('#{$target}');
382: html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
383: var base64 = canvas.toDataURL('image/png'); // 画像化
384: $('#base64').val(base64);
385: });
386: });
387:
388: EOT;
389:
390: // Leafletの場合(ブラウザによってはうまく動作しない)
391: } else {
392: $js .=<<< EOT
393: HTMLCanvasElement.prototype.getContext = function(origFn) {
394: return function(type, attribs) {
395: attribs = attribs || {};
396: attribs.preserveDrawingBuffer = true;
397: return origFn.call(this, type, attribs);
398: };
399: } (HTMLCanvasElement.prototype.getContext);
400:
401: // HTML画像化イベント登録
402: function html2image() {
403: var capture = document.querySelector('#{$target}');
404: html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
405: var base64 = canvas.toDataURL('image/png'); // 画像化
406: $('#base64').val(base64);
407: });
408: };
409:
410: // ズーム変更イベント
411: map.on('zoomend', function() {
412: html2image();
413: });
414:
415: // マップ移動イベント
416: map.on('moveend', function() {
417: html2image();
418: });
419:
420: // html2image()を発火させるために,ズームアウトして500msec後に元に戻す.
421: var zoom = map.getZoom();
422: map.setZoom(zoom - 1);
423: setTimeout(function() {
424: map.setZoom(zoom);
425: }, 500);
426:
427: EOT;
428: }
429:
430: return $js;
431: }
Leafletの場合、tilesloaded に相当するイベントがないため、ズーム変更完了イベント zoomend にフックするようにした。強制的にズームアウトし、500ミリ秒後に元ズーム値に戻す操作を行う。しかし、この方法だとブラウザによって、マップ画像が無い状態で画像化されてしまうことがあるようだ。もし対策方法をご存じの方がいたらお知らせいただきたい。
これらをJavaScriptとして生成するユーザー関数が js_html2image である。
準備:pahooTwitterAPI クラス
pahooTwitterAPI.php
19: class pahooTwitterAPI {
20: var $responses; //応答データ
21: var $webapi; //直前に呼び出したWebAPI URL
22: var $error; //エラーフラグ
23: var $errmsg; //エラーメッセージ
24: var $errcode; //エラーコード
25: var $connection;
26:
27: //OAuth用パラメータ
28: // https://apps.twitter.com/
29: var $TWTR_CONSUMER_KEY = '***************'; //Cunsumer key
30: var $TWTR_CONSUMER_SECRET = '***************'; //Consumer secret
31: var $TWTR_ACCESS_KEY = '***************'; //Access Token (oauth_token)
32: var $TWTR_ACCESS_SECRET = '***************'; //Access Token Secret (oauth_token_secret)
解説:メディア付き投稿(RAWデータ)
pahooTwitterAPI.php
590: /**
591: * バイナリデータを使ったメディア付きメッセージをツイートする.
592: * Tweetet API v2 を使用する.
593: * @param string $message 投稿メッセージ(UTF-8限定)
594: * @param array $items メディアデータ(バイナリデータ配列)
595: * @return bool TRUE:リクエスト成功/FALSE:失敗
596: */
597: function tweet_media_raw($message, $items) {
598: //メディアのアップロード
599: $media_ids = array();
600: $cnt = 0;
601: //Tweetet API v1.1 を使用する(v2にメディアアップロードが未実装のため)
602: $this->connection->setApiVersion('1.1');
603: foreach ($items as $data) {
604: $tmpname = $this->saveTempFile($data);
605: // $media = $this->connection->upload('media/upload', ['media' => $tmpname]);
606: $media = $this->connection->upload('media/upload', ['media' => $tmpname], ['chunkedUpload' => true]); //twitteroauth 7.0.0 対応
607: unlink($tmpname);
608: if (! isset($media->media_id_string)) break; //処理失敗
609: $media_ids[] = (string)$media->media_id_string;
610: $cnt++;
611: if ($cnt > 3) break; //最大4つまで
612: }
613:
614: //メディア付きツイート(Tweetet API v2 を使用する)
615: $this->connection->setApiVersion('2');
616: $option = [
617: 'text' => $message,
618: 'media' => [
619: 'media_ids' => $media_ids
620: ]
621: ];
622: // $status = $this->connection->post('tweets', $option, TRUE);
623: $status = $this->connection->post('tweets', $option, ['jsonPayload' => true]); //twitteroauth 7.0.0 対応
624: $this->webapi = 'https://api.twitter.com/2/tweets';
625:
626: //処理に成功した.
627: if ($this->isSuccess()) {
628: $this->responses = $status->data;
629: $this->errcode = NULL;
630: $this->errmsg = '';
631: $this->error = FALSE;
632: $res = TRUE;
633: //処理に失敗した.
634: } else {
635: if ($this->isAuthError() == FALSE) {
636: $this->errmsg = $status->detail;
637: $this->error = TRUE;
638: }
639: $res = FALSE;
640: }
641: return $res;
642: }
解説:Twitter(現・X)へ投稿する
earthquake.php
316: /**
317: * Twitter(現・X)投稿
318: * @param string $message 投稿文
319: * @param string $res 応答メッセージ格納用
320: * @return bool TRUE:成功/FALSE:失敗または未処理
321: */
322: function mediaTweet($message, &$res) {
323: if (! TWITTER) return FALSE;
324:
325: $ret = TRUE;
326: if (isset($_POST['base64']) && ($_POST['base64'] != '')) {
327: $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
328: $raws = array(base64_decode($base64));
329: $ptw = new pahooTwitterAPI();
330: $ptw->tweet_media_raw($message, $raws);
331: $errmsg = $ptw->errmsg;
332: $ret = ! $ptw->error;
333: $ptw = NULL;
334: if ($ret) {
335: $res = 'ツイートしました';
336: }
337: }
338: return $ret;
339: }
ブラウザからPOSTで受け取った画像データ $_POST['base64'&$x5D; はBASE64でデコードされており、冒頭に余計なヘッダ情報が付いているので、このヘッダ情報を除き( preg_replace )、バイナリデータにデコードする( base64_decode )。
続いて pahooTwitterAPI クラスを呼び出し、tweet_media_raw メソッドを使って、メッセージと画像を一気にツイートする。
準備:pahooBlueskyAPI クラス
pahooBlueskyAPI.php
15: class pahooBlueskyAPI {
16: var $pds; // PDSドメイン
17: var $webapi; // 直前に呼び出したWebAPI URL
18: var $errmsg; // エラーメッセージ
19: var $accessJwt; // accessJwt
20:
21: const INTERNAL_ENCODING = 'UTF-8'; //内部エンコーディング
22: const MAX_MESSAGE_LEN = 300; // 投稿可能なメッセージ文字数
23: const URL_LEN = 23; // メッセージ中のURL文字数(相当)
24: const MAX_IMAGE_WIDTH = 1200; // 投稿可能な最大画像幅(ピクセル)
25: const MAX_IMAGE_HEIGHT = 630; // 投稿可能な最大画像高さ(ピクセル)
26: // これより大きいときは自動縮小する
27:
28: // Bluesky API アプリパスワード
29: // https://bsky.app/
30: var $BLUESKY_HANDLE = '***************'; // ハンドル名
31: var $BLUESKY_PASSWORD = '***************'; // アプリケーション・パスワード
解説:Blueskyへ投稿する
earthquake.php
341: /**
342: * Bluesky投稿
343: * @param string $message 投稿文
344: * @param string $res 応答メッセージ格納用
345: * @return bool TRUE:成功/FALSE:失敗または未処理
346: */
347: function mediaBluesky($message, &$res) {
348: if (! BLUESKY) return FALSE;
349:
350: $ret = TRUE;
351: if (isset($_POST['base64']) && ($_POST['base64'] != '')) {
352: $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
353: $raws = array(base64_decode($base64));
354: $pbs = new pahooBlueskyAPI(BLUESKY_DOMAIN);
355: $res = $pbs->createSession();
356: $pbs->post($message, FALSE, NULL, NULL, $raws);
357: $errmsg = $pbs->geterror();
358: $ret = ! $pbs->iserror();
359: $res = $pbs->deleteSession();
360: $pbs = NULL;
361: if ($ret) {
362: $res = 'Blueskyに投稿しました';
363: }
364: }
365: return $ret;
366: }
ブラウザからPOSTで受け取った画像データ $_POST['base64'] はBASE64でデコードされており、冒頭に余計なヘッダ情報が付いているので、このヘッダ情報を除き( preg_replace )、バイナリデータにデコードする( base64_decode )。
続いて pahooBlueskyAPI クラスを呼び出し、post メソッドを使って、メッセージと画像を一気に投稿する。
解説:SNSへ投稿する(メイン・プログラム)
earthquake.php
972: // Twitter(現・X)、Blueskyへ投稿する.
973: $dt = nowDT();
974: $message =<<< EOT
975: ⚠️最近の地震情報 {$dt}現在
976:
977: (ご参考)PHPで最近の地震情報を表示する https://www.pahoo.org/e-soul/webtech/php05/php05-17-01.shtm #地震
978:
979: EOT;
980: if (TWITTER && isButton('tweet')) {
981: mediaTweet($message, $res);
982: }
983: if (BLUESKY && isButton('bluesky')) {
984: mediaBluesky($message, $res);
985: }
986:
987: // 表示コンテンツを作成する.
活用例
参考サイト
- WebAPIの登録方法:ぱふぅ家のホームページ
- PHPで住所・ランドマークから最寄り駅を求める:ぱふぅ家のホームページ
- PHPで緯度・経度から住所を求める:ぱふぅ家のホームページ
- JavaScriptでHTML表示を画像保存する:ぱふぅ家のホームページ
- HTMLとCSSでさまざまなアイコンを表示する:ぱふぅ家のホームページ
- Twitter API - WebAPIの登録方法:ぱふぅ家のホームページ
- PHPでTwitterに画像付きメッセージ投稿:ぱふぅ家のホームページ
- Bluesky API - WebAPIの登録方法:ぱふぅ家のホームページ
- PHPでBlueskyに投稿する:ぱふぅ家のホームページ
- C++ で直近の地震情報を取得する:ぱふぅ家のホームページ
- 日本の直近の地震情報:みんなの知識 ちょっと便利帳
そこで今回は、気象庁防災情報XMLから、直近の地震情報を取得するPHPスクリプトを作成してみることにする。
余震が多いことが分かりやすいように、求めたい震源の数を任意に指定できるように改良した。表示マップの種類を変えたり、マップを含めてツイートすることができる。
また、Windowsアプリを「C++ で直近の地震情報を取得する」で公開している。あわせてご試用いただきたい。
(2024年12月8日)Bluesky投稿機能を追加
(2024年6月22日)TwitterOAuth 7.0.0 対応,Twitter(現・X)ボタンを "X" に変更