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

(1/1)
東日本大震災の余震と見られる地震が収まらない。
そこで今回は、気象庁防災情報XMLから、直近の地震情報を取得するPHPスクリプトを作成してみることにする。
余震が多いことが分かりやすいように、求めたい震源の数を任意に指定できるように改良した。表示マップの種類を変えたり、マップを含めてツイートすることができる。
また、Windowsアプリを「C++ で直近の地震情報を取得する」で公開している。あわせてご試用いただきたい。

(2025年8月24日).pahooEnv 導入
(2025年6月14日)GoogleMaps JavaScript APIの変更に対応した.
(2024年12月8日)Bluesky投稿機能を追加
(2024年6月22日)TwitterOAuth 7.0.0 対応,Twitter(現・X)ボタンを "X" に変更

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

PHPで直近の地震情報を表示する
オープンストリートマップ+色別標高図+活断層図

目次

サンプル・プログラム

圧縮ファイルの内容
earthquake.phpサンプル・プログラム本体。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
pahooTwitterAPI.phpTwitter APIを利用するクラス pahooTwitterAPI。
使い方は「PHPでTwitterに投稿(ツイート)する」などを参照。include_path が通ったディレクトリに配置すること。
pahooBlueskyAPI.phpBluesky APIに関わるクラス pahooBlueskyAPI。
使い方は「PHPでPHPでBlueskyに投稿する」などを参照。include_path が通ったディレクトリに配置すること。
earthquake.php 更新履歴
バージョン 更新日 内容
5.7.0 2025/08/24 .pahooEnv導入
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化に対応
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.8.0 2025/08/10 アクセスキーなどを ".pahooEnd" に分離
6.7.1 2025/07/26 jsLine_Gmap() - bug-fix
6.7.0 2025/07/20 drawJSmap,drawGMap -- 引数 $markerLevel 追加
6.6.0 2025/07/19 drawJSmap,drawGMap,drawLeaflet -- マップ中心マーカー表示引数を追加
6.5.0 2025/06/14 GoogleMaps JavaScript APIの変更に対応
pahooCache.php 更新履歴
バージョン 更新日 内容
1.2.0 2025/09/06 cLoad() HTTPヘッダを送信できるようにした
1.1.3 2025/08/10 var→public
1.1.2 2023/07/22 bug-fix
1.1.1 2023/02/11 コメント追記
1.1 2021/04/08 simplexml_load()メソッド追加
pahooTwitterAPI.php 更新履歴
バージョン 更新日 内容
5.6.0 2025/08/13 .pahooEnv導入
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() -- メディアのシャフル機能
pahooBlueskyAPI.php 更新履歴
バージョン 更新日 内容
2.7.0 2025/08/17 reductImage,uploadBlob仕様変更←画像に余計な空白が入らないようにするため
2.6.0 2025/08/14 .pahooEnv 導入
2.5.1 2025/08/10 uploadBlob() -- bug-fix
2.5.0 2025/08/02 getOGPInformation() -- og:imageがないページに対応
2.4.0 2025/07/19 atruri2postURL() 追加, uploadBlob() 修正

プログラムの方針

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

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooGeoCode クラス

pahooGeoCode.php

  40: class pahooGeoCode {
  41:     public $items;      // 検索結果格納用
  42:     public $error;      // エラー・フラグ
  43:     public $errmsg;     // エラー・メッセージ
  44:     public $hits;       // 検索ヒット件数
  45:     public $webapi// 直前に呼び出したWebAPI URL
  46: 
  47:     // -- 以下のデータは .env ファイルに記述可能
  48:     // Google Cloud Platform APIキー
  49:     // https://cloud.google.com/maps-platform/
  50:     // ※Google Maps APIを利用しないのなら登録不要
  51:     public $GOOGLE_API_KEY_1 = '';      // HTTPリファラ用
  52:     public $GOOGLE_API_KEY_2 = '';      // IP制限用
  53:     public $GOOGLE_MAP_ID    = '';      // GoogleMaps ID
  54: 
  55:     // Yahoo! JAPAN Webサービス アプリケーションID
  56:     // https://e.developer.yahoo.co.jp/register
  57:     // ※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  58:     public $YAHOO_APPLICATION_ID = '';
  59: 
  60:     // OSM Nominatim Search API利用時に知らせるメールアドレス
  61:     // https://wiki.openstreetmap.org/wiki/JA:Nominatim#.E6.A4.9C.E7.B4.A2
  62:     // ※OSM Nominatim Search APIを利用しないのなら登録不要
  63:     public $NOMINATIM_EMAIL = '';
  64: 
  65:     // IP2Location.io APIキー
  66:     // https://www.ip2location.io/
  67:     // ※IP2Location.ioを利用しないのなら登録不要
  68:     public $IP2LOCATION_API_KEY = '';

GoogleマップやLeafletなどによる地図描画や住所検索を行うためのクラスが pahooGeoCode である。同梱のクラス・ファイル "pahooGeoCode.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooGeoCodeクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

地図や住所検索として Google を利用するのであれば Google Cloud Platform APIキーマップID が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を、IP2Location.ioを利用するのであれば「PHPでIPアドレスやホスト名から住所を求める」を、それぞれ参照されたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooCache クラス

pahooCache.php

  13: class pahooCache {
  14:     const LIFE_CACHE = (2 * 60);        // キャッシュ保持時間(デフォルト;分)
  15:     const DEF_DIRCACHE = './pcache/';   // キャッシュ・ディレクトリ(デフォルト)
  16: 
  17:     public $lifeCache;      // キャッシュ保持時間(分)(0:キャッシュしない)
  18:     public $dirCache;       // キャッシュ用ディレクトリ
  19:     public $httpHeader;     // HTTPヘッダ(空文字の時は何も送らない)
  20:     public $error;          // エラーフラグ
  21:     public $errmsg;         // エラーメッセージ
  22:     public $debug;          // デバッグ用ファイル名
  23: 
  24: /**
  25:  * コンストラクタ
  26:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  27:  * @param   int    $life キャッシュ保持時間(分)(省略可能)
  28:  * @param   string $dir キャッシュ・ディレクトリ(省略可能)
  29:  * @param   array  $httpHeader httpヘッダに渡す配列(省略可能)
  30:  *          USER AGENT偽装に用いることを想定
  31:  *          (例)
  32:  *           array(
  33:  *              'User-Agent: Mozilla/5.0(Windows NT 10.0; Win64; x64) pahooAppy/pahoo.org AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
  34:  *              'Accept-Language: ja-JP'
  35:  *          );
  36:  * @return  なし
  37: */
  38: function __construct($life=self::LIFE_CACHE, $dir=self::DEF_DIRCACHE, $httpHeader=NULL) {
  39:     if ($life < 0) {
  40:         $life = 0;
  41:     }
  42:     if (preg_match('/\/$/ui', $dir) == 0) {
  43:         $dir = $dir . '/';
  44:     }
  45:     $this->error      = FALSE;
  46:     $this->errmsg     = '';
  47:     $this->debug      = '';
  48:     $this->lifeCache  = $life;
  49:     $this->dirCache   = $dir;
  50:     $this->httpHeader = $httpHeader;
  51: 
  52:     // PHP5以上であることを調べる.
  53:     if (! $this->isphp5over()) {
  54:         $this->error   = TRUE;
  55:         $this->errmsg  = '動作にはPHP5以上が必要です';
  56:         return;
  57:     }
  58: 
  59:     // キャッシュ・ディレクトリが無ければ作成する.
  60:     if (! is_dir($this->dirCache)) {
  61:         $res = mkdir($this->dirCache, 0744);
  62:         if ($res == FALSE) {
  63:             $this->error   = TRUE;
  64:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
  65:             return;
  66:         }
  67:     }
  68: }

クラウド連携や相手先サイトの公開データを利用する際、こちらのアプリを起動する都度、APIを呼び出したりデータをダウンロードするのでは、相手サーバに負荷をかけてしまう。
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが pahooCacheクラス である。同梱のクラス・ファイル "pahooCache.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooCacheクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

pahooCacheクラス の注意ポイントは、キャッシュ時間(単位:分)とキャッシュを保存するディレクトリをコンストラクタで指定している点だ。これらはプログラムによって変わるものである。インスタンス化するときの値は、pahooCacheクラス を利用するメイン・プログラムの方で解説する。

サイトによっては、User-Agent などを必要とすることがあるだろう。そこで、第3引数に HTTPヘッダ として送信するデータを配列で渡すことができるようにした。配列の構造はコメントを参照していただきたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

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

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)
PHPで直近の地震情報を表示する
Googleマップ表示

準備:各種定数など

earthquake.php

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

表示に関わる各種パラメータは定数を defineしている。【変更不可】の記載のないものは、適宜変更してかまわない。
表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVIC に値を設定すること。

出力結果を 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

 406: /**
 407:  * 気象庁防災情報XMLから震源・震度に関する情報URLを取得
 408:  * @param   int    $mode    0:最新1件のみ取得,1:すべての情報URL取得
 409:  * @param   array  $urls    URL格納配列
 410:  * @param   string $errmsg  エラーメッセージ格納用
 411:  * @return  bool TRUE:取得成功/FALSE:取得失敗
 412: */
 413: function jma_getLastEarthquakeURLs($mode, &$urls, &$errmsg) {
 414:     // URLパターン
 415:     $vxse53 = '/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VXSE53\_[0-9]+\.xml/ui';
 416: 
 417:     // 随時フィードの解析
 418:     $cnt = 0;
 419:     $pcc = new pahooCache(LIFE_CACHE_FEED, DIR_CACHE_FEED);
 420:     $xml = $pcc->simplexml_load(FEED);
 421:     // レスポンス・チェック
 422:     if ($pcc->iserror() || !isset($xml->entry)) {
 423:         $errmsg = '気象庁防災情報XMLにアクセスできません';
 424:         return FALSE;
 425:     }
 426:     foreach ($xml->entry as $node) {
 427:         // URLを取得
 428:         if (preg_match($vxse53, $node->id, $arr> 0) {
 429:             $urls[$cnt] = $arr[0];
 430:             $cnt++;
 431:         }
 432:     }
 433:     $pcc = NULL;
 434: 
 435:     // 長期フィードの解析
 436:     $pcc = new pahooCache(LIFE_CACHE_FEED_L, DIR_CACHE_FEED_L);
 437:     $xml = $pcc->simplexml_load(FEED_L);
 438:     // レスポンス・チェック
 439:     if ($pcc->iserror() || !isset($xml->entry)) {
 440:         $errmsg = '気象庁防災情報XMLにアクセスできません';
 441:         return FALSE;
 442:     }
 443:     foreach ($xml->entry as $node) {
 444:         // URLを取得
 445:         if (preg_match($vxse53, $node->id, $arr> 0) {
 446:             if (array_search($arr[0], $urls) === FALSE) {
 447:                 $urls[$cnt] = $arr[0];
 448:                 $cnt++;
 449:             }
 450:         }
 451:     }
 452:     $pcc = NULL;
 453: 
 454:     // エラー・チェック
 455:     if ($cnt == 0) {
 456:         $errmsg = '直近の地震情報はありません';
 457:         return FALSE;
 458:     }
 459: 
 460:     // URLを日時の新しい順にソート
 461:     rsort($urls);
 462: 
 463:     return TRUE;
 464: }

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

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

earthquake.php

 466: /**
 467:  * 地震情報取得(気象庁防災情報XMLから)
 468:  * @param   object $pgc     pahooGeoCodeオブジェクト
 469:  * @param   array  $items   地震情報を格納する配列
 470:  * @param   string $urls    情報XMLのURLを格納する配列
 471:  * @param   string $errmsg  エラーメッセージ格納用
 472:  * @param   int    $count   取得件数(省略時=1)
 473:  * @return  bool TRUE:取得成功/FALSE:失敗
 474: */
 475: function get_earthquake($pgc, &$items, &$urls, &$errmsg, $count=1) {
 476:     // 名前空間
 477:     define('JMX_EB', 'http://xml.kishou.go.jp/jmaxml1/elementBasis1/');
 478:     // マッチングパターン
 479:     $pat1 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:([0-9]+)/ui';      // 年月日時分
 480:     $pat2 = '/([\+\-][0-9\.]+)([\+\-][0-9\.]+)([\-\+\/])([0-9]*)/ui';   // 緯度・経度・深さ
 481: 
 482:     // オブジェクト生成
 483:     $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA);
 484: 
 485:     // 最新の震源・震度に関する情報URLを取得
 486:     $urls = array();
 487:     $mode = ($count == 1? 0 : 1;
 488:     jma_getLastEarthquakeURLs($mode, $urls, $errmsg);
 489:     if ($errmsg !'')      return FALSE;
 490: 
 491:     foreach ($urls as $key=>$vxse53) {
 492:         // 取得件数が上限だったらループ脱出
 493:         if ($key >$count)     break;
 494: 
 495:         // 震源情報の取得
 496:         $xml = $pcc->simplexml_load($vxse53);
 497:         // レスポンス・チェック
 498:         if ($pcc->iserror() || !isset($xml->Body->Earthquake)) {
 499:             $errmsg = '気象庁防災情報XMLから最新の震源・震度情報を取得できません';
 500:             return FALSE;
 501:         }
 502: 
 503:         // 地震発生日時の取得
 504:         if ((preg_match($pat1, (string)$xml->Body->Earthquake->OriginTime, $arr> 0&& isset($arr[5])) {
 505:             $items[$key]['year']    = (int)$arr[1];
 506:             $items[$key]['month']   = (int)$arr[2];
 507:             $items[$key]['day']     = (int)$arr[3];
 508:             $items[$key]['hour']    = (int)$arr[4];
 509:             $items[$key]['minuite'] = (int)$arr[5];
 510:         } else {
 511:             $errmsg = '気象庁防災情報XMLから最新の震源・震度情報を取得できません';
 512:             return FALSE;
 513:         }
 514: 
 515:         // 震源地の取得
 516:         if (isset($xml->Body->Earthquake->Hypocenter->Area->Name)) {
 517:             $items[$key]['location'] = $xml->Body->Earthquake->Hypocenter->Area->Name;
 518:         } else {
 519:             $items[$key]['location'] = '不明';
 520:         }
 521:         if ($items[$key]['location'] == '') {
 522:             $items[$key]['location'] = '不明';
 523:         }
 524:         $node = $xml->Body->Earthquake->Hypocenter->Area->children(JMX_EB);
 525:         if (preg_match($pat2, (string)$node->Coordinate, $arr)) {
 526:             if (isset($arr[1]) && isset($arr[2])) {
 527:                 list($items[$key]['longitude'], $items[$key]['latitude']) = $pgc->tokyo_wgs84((float)$arr[2], (float)$arr[1]);
 528:             } else {
 529:                 $items[$key]['latitude'] = $items[$key]['longitude'] = '不明';
 530:             }
 531:             if (isset($arr[3]) && ($arr[3] == '/')) {
 532:                 $items[$key]['depth']  = '不明';
 533:             } else if (isset($arr[4])) {
 534:                 $items[$key]['depth']  = (float)$arr[4];
 535:             } else {
 536:                 $items[$key]['depth']  = '不明';
 537:             }
 538:         }
 539: 
 540:         // マグニチュードの取得
 541:         $node = $xml->Body->Earthquake->children(JMX_EB);
 542:         if (isset($node->Magnitude)) {
 543:             $items[$key]['magnitude']  = (float)$node->Magnitude;
 544:         } else {
 545:             $items[$key]['magnitude']  = '不明';
 546:         }
 547: 
 548:         // 震度を取得
 549:         if (isset($xml->Body->Intensity->Observation->MaxInt)) {
 550:             $items[$key]['maxintensity'] = (int)$xml->Body->Intensity->Observation->MaxInt;
 551:         } else {
 552:             $items[$key]['maxintensity'] = '不明';
 553:         }
 554:     }
 555: 
 556:     // オブジェクト解法
 557:     $pcc = NULL;
 558: 
 559:     return TRUE;
 560: }

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

pahooGeoCode.php

 609: /**
 610:  * 日本測地系を世界測地系に変換する
 611:  * @param   float $long 経度(日本測地系)
 612:  * @param   float $lat  緯度(日本測地系)
 613:  * @return  float array(経度,緯度)(世界測地系)
 614: */
 615: function tokyo_wgs84($long, $lat) {
 616:     $glong = $long - $lat * 0.000046038 - $long * 0.000083043 + 0.010040;
 617:     $glat  = $lat  - $lat * 0.00010695  + $long * 0.000017464 + 0.0046017;
 618:     return array($glong, $glat);
 619: }

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

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

earthquake.php

 573: /**
 574:  * 地震情報をマッピング情報に変換
 575:  * @param   array  $items   地震情報を格納した配列
 576:  * @param   array  $points  マッピング情報を格納する配列
 577:  * @return  int 変換したマッピング情報の件数
 578: */
 579: function info2points(&$items, &$points) {
 580:     $i = 0;
 581:     $j = 0;
 582:     foreach ($items as $i=>$item) {
 583:         // 同じマッピング位置があるかどうか
 584:         $flag = FALSE;
 585:         for ($k = 0$k < $j$k++) {
 586:             if (($items[$i]['latitude'] == $points[$k + 1]['latitude']) && ($items[$i]['longitude'] == $points[$k + 1]['longitude'])) {
 587:                 $flag = TRUE;
 588:                 $points[$k + 1]['description'.'<br /><hr />';
 589:                 break;
 590:             }
 591:         }
 592:         // 新規のマッピング位置
 593:         if (! $flag) {
 594:             $k = $j;
 595:             $points[$k + 1]['latitude']    = $items[$i]['latitude'];
 596:             $points[$k + 1]['longitude']   = $items[$i]['longitude'];
 597:             $points[$k + 1]['title'] = '';
 598:             $points[$k + 1]['description'] = '';
 599:             $j++;
 600:         }
 601:         $items[$i]['id'] = num2alpha($k + 1);
 602:         $dt = makeDateTime($items[$i]);
 603:         $latitude  = sprintf('%.1f', $items[$i]['latitude']);
 604:         $longitude = sprintf('%.1f', $items[$i]['longitude']);
 605:         if (is_numeric($items[$i]['magnitude'])) {
 606:             $magnitude = sprintf('%.1f', $items[$i]['magnitude']);
 607:         } else {
 608:             $magnitude = $items[$i]['magnitude'];
 609:         }
 610:         if (is_numeric($items[$i]['depth'])) {
 611:             if ($items[$i]['depth'] == 0) {
 612:                 $depth = 'ごく浅い';
 613:             } else {
 614:                 $depth = sprintf('約%dkm', $items[$i]['depth'] / 1000);
 615:             }
 616:         } else {
 617:             $depth = $items[$i]['depth'];
 618:         }
 619:         $points[$k + 1]['description'.=<<< EOT
 620: 発生日時:{$dt}<br />震源地:{$items[$i]['location']}<br />震源の位置:北緯 {$latitude}度,東経 {$longitude}度<br />震源の深さ:{$depth}<br />地震の規模:マグニチュード {$magnitude}<br />最大震度:{$items[$i]['maxintensity']}
 621: EOT;
 622:         // 打ち切り条件
 623:         if ($j >26)   break;
 624:     }
 625: 
 626:     return $j;
 627: }

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

解説:地図描画について

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

解説:オーバーレイ表示(Leaflet選択時のみ)

マップで Leaflet を選択したとき、オーバーレイ地図を表示できるようにした。
オーバーレイ地図としては、地理院地図の 色別標高図活断層図(都市圏活断層図)治水地形分類図 更新版(2007~2020年)の3つを用意した。

pahooGeoCode.php

1987:     // 地理院地図:色別標高図(オーバーレイ)
1988:     let GSIELEV = new L.tileLayer(
1989:         'https://cyberjapandata.gsi.go.jp/xyz/relief/{z}/{x}/{y}.png',
1990:         {
1991:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
1992:             opacity: 0.6,
1993:             minZoom: 5,
1994:             maxZoom: 15,
1995:             name: 'GSIELEV'
1996:         });
1997:     // 地理院地図:活断層図(オーバーレイ)
1998:     let GSIFAULT = new L.tileLayer(
1999:         'https://cyberjapandata.gsi.go.jp/xyz/afm/{z}/{x}/{y}.png',
2000:         {
2001:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
2002:             opacity: 0.6,
2003:             minZoom: 11,
2004:             maxZoom: 16,
2005:             name: 'GSIFAULT'
2006:         });
2007:     // 地理院地図:治水地形分類図 更新版(オーバーレイ)
2008:     let GSIFLOOD = new L.tileLayer(
2009:         'https://cyberjapandata.gsi.go.jp/xyz/lcmfc2/{z}/{x}/{y}.png',
2010:         {
2011:             attribution: "<a href='https://maps.gsi.go.jp/development/ichiran.html' target='_blank'>地理院タイル</a>",
2012:             opacity: 0.6,
2013:             minZoom: 11,
2014:             maxZoom: 16,
2015:             name: 'GSIFLOOD'
2016:         });
2017: 
2018:     // baseMapsオブジェクトにオーバーレイ設定
2019:     let overlayMaps = {
2020:         "色別標高図"     : GSIELEV,
2021:         "活断層図"       : GSIFAULT,
2022:         "治水地形分類図" : GSIFLOOD,
2023:     };
2024: 
2025:     // layersコントロールにbaseMapsオブジェクトを設定して地図に追加
2026:     L.control.layers(baseMaps, overlayMaps).addTo(map);
2027:     {$type}.addTo(map);
2028: {$addoverlay}

PHPで直近の地震情報を表示する
オーバーレイ選択
オーバレイは複数指定することが可能で、マップの右上のアイコンをクリックして、チェックボックスで指定する。

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

earthquake.php

 913: // パラメータ
 914: $number = getParam('number', FALSE, DEF_NUMBER);
 915: $number = trim($number);
 916: if (preg_match('/^[0-9]+$/i', $number> 0) {
 917:     $errmsg = '';
 918:     if (($number < 1|| ($number > MAX_NUMBER)) {
 919:         $errmsg = sprintf('判定できる整数の範囲は,1から%dまでです', MAX_NUMBER);
 920:     }
 921: else {
 922:     $errmsg = '数値は正の整数(自然数)を指定してください';
 923: }
 924: $zoom     = getParam('zoom',     FALSE, DEF_ZOOM);
 925: $zoom     = ($zoom == ''? DEF_ZOOM : $zoom;
 926: $type     = getParam('type',     FALSE, DEF_TYPE);
 927: $type     = ($type == ''? DEF_TYPE : $type;
 928: $overlays = getParam('overlays', FALSE, DEF_OVERLAYS);
 929: $overlays = ($overlays == ''? DEF_OVERLAYS : $overlays;

URLパラメータを使って
earthquake.php?number=10
のようにすることで、number で指定した値が求めたい震源の数になる。
また、
earthquake.php?number=10&zoom=8&type=GSISTD&overlays=GSIELEV,GSIFAULT
のように指定すると、震源数が10、拡大率が8、ベースマップが地理院地図、オーバーレイは色別標高図と活断層図で表示する。

解説:SNS投稿機能

PHPによるSNS投稿機能の概念図
PHPによるSNS投稿機能の概念図
表示したコンテンツを画像として、メッセージと一緒にボタン1つでSNSに投稿する機能を追加した。流れは次の通りである。
コンテンツを描画しているのはクライアントにあるブラウザ(レンダリングエンジン)であることから、クライアント側で画像を作成し、サーバ側で SNS の API をコールするという方針とした。
  1. ブラウザはサーバにコンテンツ描画をリクエストする。
  2. サーバはデータサイトからデータ取得する。(サーバキャッシュにデータがあればそれを利用する)
  3. サーバはコンテンツ描画スクリプトを生成する。
  4. サーバはブラウザへレスポンス(HTML文)を返す。
  5. ブラウザはコンテンツをレンダリングする。
  6. ブラウザはレンダリングしたコンテンツを画像データとしてサーバへアップロードする。
  7. サーバは SNS の API を使ってツイートする。
  8. サーバは SNS へメッセージと画像を送る。
  9. サーバはブラウザへレスポンス(HTML文)を返す。
ブラウザでレンダリングしたグラフを画像に変換する処理は、JavaScriptの html2canvas ライブラリを利用した。このライブラリの使い方は後述する。投稿するSNSのうち、Twitter(現・X) については、「PHPでTwitterに投稿(ツイート)する」で作成した pahooTwitterAPI クラスを利用する。Bluesky については、「PHPでBlueskyに投稿する」で作成した pahooBlueskyAPI クラスを利用する。

解説:html2canvasライブラリ

earthquake.php

 167: <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
 168: <script>
 169: function mytweet() {
 170:     $('#tweet').val('1');
 171:     document.myform.submit();
 172: }

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

earthquake.php

 883: <div id="{$target}" name="{$target}" style="width:{$width2}px;">
 884: <p>
 885: ⚠️最近の地震情報 {$dt}現在
 886: </p>
 887: {$html}
 888: </div>

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

earthquake.php

 341: /**
 342:  * HTMLオブジェクトの画像化
 343:  * @param   なし
 344:  * @return  string JavaScriptコード
 345: */
 346: function js_html2image() {
 347:     $target = TARGET;
 348:     $js = '';
 349: 
 350:     // Googleマップの場合
 351:     if (MAPSERVICE == 0) {
 352:         $js .=<<< EOT
 353: google.maps.event.addListener(map, 'tilesloaded', function() {
 354:     var capture = document.querySelector('#{$target}');
 355:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
 356:         var base64 = canvas.toDataURL('image/png');     // 画像化
 357:         $('#base64').val(base64);
 358:     });
 359: });
 360: 
 361: EOT;
 362: 
 363:     // Leafletの場合(ブラウザによってはうまく動作しない)
 364:     } else {
 365:         $js .=<<< EOT
 366: HTMLCanvasElement.prototype.getContext = function(origFn) {
 367:     return function(type, attribs) {
 368:         attribs = attribs || {};
 369:         attribs.preserveDrawingBuffer = true;
 370:         return origFn.call(this, type, attribs);
 371:     };
 372: } (HTMLCanvasElement.prototype.getContext);
 373: 
 374: // HTML画像化イベント登録
 375: function html2image() {
 376:     var capture = document.querySelector('#{$target}');
 377:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
 378:         var base64 = canvas.toDataURL('image/png');     // 画像化
 379:         $('#base64').val(base64);
 380:     });
 381: };
 382: 
 383: // ズーム変更イベント
 384: map.on('zoomend', function() {
 385:     html2image();
 386: });
 387: 
 388: // マップ移動イベント
 389: map.on('moveend', function() {
 390:     html2image();
 391: });
 392: 
 393: // html2image()を発火させるために,ズームアウトして500msec後に元に戻す.
 394: var zoom = map.getZoom();
 395: map.setZoom(zoom - 1);
 396: setTimeout(function() {
 397:     map.setZoom(zoom);
 398: }, 500);
 399: 
 400: EOT;
 401:     }
 402: 
 403:     return $js;
 404: }

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

準備:pahooTwitterAPI クラス

pahooTwitterAPI.php

  11: // TwitterOAuth クラスをロードする.
  12: $version = explode('.', phpversion());
  13: if ($version[0>8) {
  14:     require __DIR__ . '/vendor/autoload.php';
  15: }
  16: use Abraham\TwitterOAuth\TwitterOAuth;
  17: 

Twitter API」を利用するためのクラスファイルが "pahooTwitterAPI.php" である。同梱のクラス・ファイル "pahooTwitterAPI.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooTitterAPIクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

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

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

OAuth認証 および Twitter API へのアクセスには、TwitterOAuth を利用する。導入方法は公式サイトにあるように composer を使うことを推奨している。
TwitterOAuth 7.0.0PHP 8.4(2024年11月21日リリース)でいくつかのエラーが発生する。これは、引数に暗黙の null を指定することが非推奨になったためで、プログラムの改修が行われるまでは PHP 8.3 で利用することをおすすめする。

TwitterOAuth クラスをロードする。composer でインストールされたフォルダ "\vendor" を "pahooTwitterAPI.php" と同じフォルダへコピーすること。

pahooTwitterAPI.php

  37: /**
  38:  * コンストラクタ
  39:  * @param   なし
  40:  * @return  なし
  41: */
  42: function __construct() {
  43:     if (isset($_ENV['PAHOO_TWTR_CONSUMER_KEY'])) {
  44:         $this->TWTR_CONSUMER_KEY = $_ENV['PAHOO_TWTR_CONSUMER_KEY'];
  45:     }
  46:     if (isset($_ENV['PAHOO_TWTR_CONSUMER_SECRET'])) {
  47:         $this->TWTR_CONSUMER_SECRET = $_ENV['PAHOO_TWTR_CONSUMER_SECRET'];
  48:     }
  49:     if (isset($_ENV['PAHOO_TWTR_ACCESS_KEY'])) {
  50:         $this->TWTR_ACCESS_KEY = $_ENV['PAHOO_TWTR_ACCESS_KEY'];
  51:     }
  52:     if (isset($_ENV['PAHOO_TWTR_ACCESS_SECRET'])) {
  53:         $this->TWTR_ACCESS_SECRET = $_ENV['PAHOO_TWTR_ACCESS_SECRET'];
  54:     }
  55: 
  56:     // プロパティを初期化する.
  57:     $this->responses = array();
  58:     $this->webapi    = '';
  59:     $this->error     = FALSE;
  60:     $this->errmsg    = '';
  61:     $this->errcode   = 0;
  62: 
  63:     $this->connection = new TwitterOAuth($this->TWTR_CONSUMER_KEY, $this->TWTR_CONSUMER_SECRET, $this->TWTR_ACCESS_KEY, $this->TWTR_ACCESS_SECRET);
  64:     // v2使用を宣言
  65:     $this->connection->setApiVersion('2');
  66: 
  67:     // PHP8.4で暗黙的にnullableを指定することが非推奨になったため
  68:     // TwitterOAuthが対応するまでの応急処置
  69:     error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
  70: }

TwitterOAuth クラスは pahooTwitterAPI のコンストラクタでインスタンス化しておく。

pahooTwitterAPI.php

  30:     // OAuth用パラメータ
  31:     // https://apps.twitter.com/
  32:     public $TWTR_CONSUMER_KEY    = '';      // Cunsumer key
  33:     public $TWTR_CONSUMER_SECRET = '';      // Consumer secret
  34:     public $TWTR_ACCESS_KEY      = '';      // Access Token (oauth_token)
  35:     public $TWTR_ACCESS_SECRET   = '';      // Access Token Secret (oauth_token_secret)
  36: 

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

pahooTwitterAPI.php

 607: /**
 608:  * バイナリデータを使ったメディア付きメッセージをツイートする.
 609:  * Tweetet API v2 を使用する.
 610:  * @param   string $message 投稿メッセージ(UTF-8限定)
 611:  * @param   array  $items   メディアデータ(バイナリデータ配列)
 612:  * @return  bool TRUE:リクエスト成功/FALSE:失敗
 613: */
 614: function tweet_media_raw($message, $items) {
 615:     // メディアのアップロード
 616:     $media_ids = array();
 617:     $cnt = 0;
 618:     // Tweetet API v1.1 を使用する(v2にメディアアップロードが未実装のため)
 619:     $this->connection->setApiVersion('1.1'); 
 620:     foreach ($items as $data) {
 621:         $tmpname = $this->saveTempFile($data);
 622: //      $media = $this->connection->upload('media/upload', ['media' => $tmpname]);
 623:         $media = $this->connection->upload('media/upload', ['media' => $tmpname], ['chunkedUpload' => true]);   // twitteroauth 7.0.0 対応
 624:         unlink($tmpname);
 625:         if (! isset($media->media_id_string))   break;      // 処理失敗
 626:         $media_ids[] = (string)$media->media_id_string;
 627:         $cnt++;
 628:         if ($cnt > 3)   break;      // 最大4つまで
 629:     }
 630: 
 631:     // メディア付きツイート(Tweetet API v2 を使用する)
 632:     $this->connection->setApiVersion('2'); 
 633:     $option = [
 634:         'text' => $message,
 635:         'media' => [
 636:             'media_ids' => $media_ids
 637:         ]
 638:     ];
 639: //  $status = $this->connection->post('tweets', $option, TRUE);
 640:     $status = $this->connection->post('tweets', $option, ['jsonPayload' => true]);  // twitteroauth 7.0.0 対応
 641:     $this->webapi = 'https://api.twitter.com/2/tweets';
 642: 
 643:     // 処理に成功した.
 644:     if ($this->isSuccess()) {
 645:         $this->responses = $status->data;
 646:         $this->errcode   = NULL;
 647:         $this->errmsg    = '';
 648:         $this->error     = FALSE;
 649:         $res = TRUE;
 650:     // 処理に失敗した.
 651:     } else {
 652:         if ($this->isAuthError() == FALSE) {
 653:             $this->errmsg = $status->detail;
 654:             $this->error  = TRUE;
 655:         }
 656:         $res = FALSE;
 657:     }
 658:     return $res;
 659: }

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

解説:Twitter(現・X)へ投稿する

earthquake.php

 289: /**
 290:  * Twitter(現・X)投稿
 291:  * @param   string $message 投稿文
 292:  * @param   string $res     応答メッセージ格納用
 293:  * @return  bool TRUE:成功/FALSE:失敗または未処理
 294: */
 295: function mediaTweet($message, &$res) {
 296:     if (! TWITTER)  return FALSE;
 297: 
 298:     $ret = TRUE;
 299:     if (isset($_POST['base64']) && ($_POST['base64'!'')) {
 300:         $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
 301:         $raws = array(base64_decode($base64));
 302:         $ptw = new pahooTwitterAPI();
 303:         $ptw->tweet_media_raw($message, $raws);
 304:         $errmsg = $ptw->errmsg;
 305:         $ret = ! $ptw->error;
 306:         $ptw = NULL;
 307:         if ($ret) {
 308:             $res = 'ツイートしました';
 309:         }
 310:     }
 311:     return $ret;
 312: }

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

準備:pahooBlueskyAPI クラス

pahooBlueskyAPI.php

Bluesky API」を利用するためのクラスファイルが "pahooBlueskyAPI.php" である。同梱のクラス・ファイル "pahooBlueskyAPI.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooBlueskyAPIクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

Bluesky API」を利用するために、ハンドル名アプリケーション・パスワード が必要で、入手方法は「Bluesky API - WebAPIの登録方法」を参照されたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

解説:Blueskyへ投稿する

earthquake.php

 314: /**
 315:  * Bluesky投稿
 316:  * @param   string $message 投稿文
 317:  * @param   string $res     応答メッセージ格納用
 318:  * @return  bool TRUE:成功/FALSE:失敗または未処理
 319: */
 320: function mediaBluesky($message, &$res) {
 321:     if (! BLUESKY)  return FALSE;
 322: 
 323:     $ret = TRUE;
 324:     if (isset($_POST['base64']) && ($_POST['base64'!'')) {
 325:         $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
 326:         $raws = array(base64_decode($base64));
 327:         $pbs = new pahooBlueskyAPI(BLUESKY_DOMAIN);
 328:         $res = $pbs->createSession();
 329:         $pbs->post($message, FALSE, NULL, NULL, $raws);
 330:         $errmsg = $pbs->geterror();
 331:         $ret = ! $pbs->iserror();
 332:         $res = $pbs->deleteSession();
 333:         $pbs = NULL;
 334:         if ($ret) {
 335:             $res = 'Blueskyに投稿しました';
 336:         }
 337:     }
 338:     return $ret;
 339: }

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

解説:SNSへ投稿する(メイン・プログラム)

earthquake.php

 945: // Twitter(現・X)、Blueskyへ投稿する.
 946: $dt = nowDT();
 947: $message =<<< EOT
 948: ⚠️最近の地震情報 {$dt}現在
 949: 
 950: (ご参考)PHPで最近の地震情報を表示する https://www.pahoo.org/e-soul/webtech/php05/php05-17-01.shtm #地震
 951: 
 952: EOT;
 953: if (TWITTER && isButton('tweet')) {
 954:     mediaTweet($message, $res);
 955: }
 956: if (BLUESKY && isButton('bluesky')) {
 957:     mediaBluesky($message, $res);
 958: }
 959: 
 960: // 表示コンテンツを作成する.

メイン・プログラムでは、mediaTweetmediaBluesky を呼び出し、メッセージと画像をSNSに投稿する。メッセージの内容は自由に変更していただいて構わない。

活用例

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

参考サイト

(この項おわり)
header