PHPで指定河川の洪水予報を表示する

(1/1)
これまで国土交通省「川の防災情報」を参照し、河川の水位が氾濫注意水位以上の地点をマッピングするプログラムを紹介してきたが、サイト・リニューアルに伴い利用できなくなったため、PHPを使って気象庁防災情報XMLから指定河川の洪水予報をマッピングするPHPプログラムに作り替えた。
表示マップの種類を変えたり、マップを含めてツイートすることができる。

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

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

PHPで指定河川の洪水予報を表示する
Googleマップ

目次

サンプル・プログラム

圧縮ファイルの内容
jmaRiverFloodForecast.phpサンプル・プログラム本体。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
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 が通ったディレクトリに配置すること。
jmaRiverFloodForecast.php 更新履歴
バージョン 更新日 内容
1.6.0 2025/08/22 count オプションを追加
1.5.0 2025/08/11 pahooInputData.php 導入
1.4.0 2024/11/01 Bluesky投稿機能を追加, isButton()修正
1.3.0 2024/06/23 Twitter(現・X)ボタンを "X" に変更
1.2.3 2023/09/20 js_html2image()--Leaflet用html2image()発火プロセス見直し
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.8.0 2025/08/10 アクセスキーなどを ".env" に分離
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.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()メソッド追加
1.0 2021/04/02 初版
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() 修正

準備: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 $error;          // エラーフラグ
  20:     public $errmsg;         // エラーメッセージ
  21:     public $debug;          // デバッグ用ファイル名
  22: 
  23: /**
  24:  * コンストラクタ
  25:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  26:  * @param   int    $life キャッシュ保持時間(分)(省略可能)
  27:  * @param   string $dir キャッシュ・ディレクトリ(省略可能)
  28:  * @return  なし
  29: */
  30: function __construct($life=self::LIFE_CACHE, $dir=self::DEF_DIRCACHE) {
  31:     if ($life < 0) {
  32:         $life = 0;
  33:     }
  34:     if (preg_match('/\/$/ui', $dir) == 0) {
  35:         $dir = $dir . '/';
  36:     }
  37:     $this->error      = FALSE;
  38:     $this->errmsg     = '';
  39:     $this->debug      = '';
  40:     $this->lifeCache = $life;
  41:     $this->dirCache  = $dir;
  42: 
  43:     // PHP5以上であることを調べる.
  44:     if (! $this->isphp5over()) {
  45:         $this->error   = TRUE;
  46:         $this->errmsg  = '動作にはPHP5以上が必要です';
  47:         return;
  48:     }
  49: 
  50:     // キャッシュ・ディレクトリが無ければ作成する.
  51:     if (! is_dir($this->dirCache)) {
  52:         $res = mkdir($this->dirCache, 0744);
  53:         if ($res == FALSE) {
  54:             $this->error   = TRUE;
  55:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
  56:             return;
  57:         }
  58:     }
  59: }

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

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

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)

VXKOの構造

指定河川の洪水予報に関する情報XML VXKO の構造は下記の通りである。
ここから必要な情報を取り出す。
VXKOの構造(xml) Report Control Title 指定河川洪水予報に関する情報 DateTime 作成日時 Status ステータス EditorialOffice 編集者 PublishingOffice 配信者 Head Title 標題 ReportDateTime 発表日時 TargetDateTime 基点日時 EventID 識別情報 InfoType 情報系大雨 Serial 情報番号 InfoKind スキーマの運用種別情報 InfoKindVersion スキーマの運用種別のバージョン情報 Headline Text 見出し Body Warning Item Kind Property Type 主文1 Text 主文1の内容 Kind Property Type 主文2 Text 主文2の内容 Areas Area Name 河川名 Code 河川コード Item Kind Property Type 浸水想定地区 Areas Area Name 観測所名称1 Code 観測所コード1 Prefecture 都道府県名1 PrefectureCode 都道府県コード1 City 市町村名1 CityCode 市町村コード1 Area Name 観測所名称2 Code 観測所コード2 Prefecture 都道府県名2 PrefectureCode 都道府県コード2 City 市町村名2 CityCode 市町村コード2 MeteorologicalInfos MeteorologicalInfo DateTime 予報・観測の基点時刻 Item Kind Property Type 雨量 Text 雨量の予報・観測文

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

jmaRiverFloodForecast.php

  71: // 地図描画サービスの選択
  72: //    0:Google
  73: //    2:地理院地図・OSM
  74: define('MAPSERVICE', 2);
  75: 
  76: // 住所検索サービスの選択
  77: //    0:Google
  78: //    1:Yahoo!JAPAN
  79: //   11:HeartRails Geo API
  80: //   12:OSM Nominatim Search API
  81: define('GEOSERVICE', 1);

表示する地図は、Googleマップ地理院地図・オープンストリートマップ(OSM)から選べる。あらかじめ、定数 MAPSERVICE に値を設定すること。
観測所の都道府県・市町村名からマッピング位置を特定するための住所検索サービスは、GoogleYahoo!JAPANHeartRails Geo APIOSM Nominatim Search API から選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
PHPで直近の地震情報を表示する
オープンストリートマップ+色別標高図+活断層図

準備:各種定数など

jmaRiverFloodForecast.php

  60: // 各種定数(START) ===========================================================
  61: 
  62: // Twitter(現・X)投稿ボタン  TRUE:有効,FALSE:無効
  63: define('TWITTER', FALSE);
  64: 
  65: // Bluesky投稿ボタン  TRUE:有効,FALSE:無効
  66: define('BLUESKY', FALSE);
  67: 
  68: // 画像化したいオブジェクト
  69: define('TARGET', 'target');
  70: 
  71: // 地図描画サービスの選択
  72: //    0:Google
  73: //    2:地理院地図・OSM
  74: define('MAPSERVICE', 2);
  75: 
  76: // 住所検索サービスの選択
  77: //    0:Google
  78: //    1:Yahoo!JAPAN
  79: //   11:HeartRails Geo API
  80: //   12:OSM Nominatim Search API
  81: define('GEOSERVICE', 1);
  82: 
  83: // マップの表示サイズ(単位:ピクセル)
  84: define('MAP_WIDTH',  640);
  85: define('MAP_HEIGHT', 480);
  86: // マップID
  87: define('MAPID', 'map_id');
  88: // 初期値
  89: define('DEF_LATITUDE',  38.82);             // 緯度
  90: define('DEF_LONGITUDE', 138.28);            // 経度
  91: define('DEF_ZOOM',      5);                 // ズーム
  92: define('DEF_TYPE',     'ROADMAP');          // マップタイプ
  93: define('DEF_OVERLAYS', 'GSIELEV,GSIFAULT'); // オーバーレイ(Leaflet使用時のみ)
  94: define('INFO_WIDTH', (MAP_WIDTH * 0.75));   // 情報ウィンドウの最大幅
  95: define('INFO_OFFSET_X',   0);           // 情報ウィンドウのオフセット位置(X)
  96: define('INFO_OFFSET_Y', -25);           // 情報ウィンドウのオフセット位置(Y)
  97: 
  98: // キャッシュ保持時間(分) 0:キャッシュしない
  99: // 気象庁へのアクセス負荷軽減のため,適切な時間を設定してください.
 100: define('LIFE_CACHE_FEED',     5);       // 高頻度 - 随時フィードに対して
 101: define('LIFE_CACHE_FEED_L', 120);       // 長期 - 随時フィードに対して
 102: define('LIFE_CACHE_DATA',   720);       // 指定河川洪水予報情報に対して
 103: 
 104: // キャッシュ・ディレクトリ
 105: // 書き込み可能で,外部からアクセスされないディレクトリを指定してください.
 106: // 天気予報系のプログラムとは別のディレクトリを指定してください.
 107: define('DIR_CACHE_FEED',   './pcache1/');
 108: define('DIR_CACHE_FEED_L', './pcache2/');
 109: define('DIR_CACHE_DATA',   './pcache3/');
 110: 
 111: // これより古い情報は無視する(単位:時間)
 112: define('INTERVAL', 24);
 113: 
 114: // 最大プロット数
 115: define('MAX_MARKERS', 999);
 116: 
 117: // 警戒レベルのアイコン
 118: define('ICON_CAUSION', 'https://maps.google.com/mapfiles/ms/micons/red-dot.png');
 119: // 予報アイコン
 120: define('ICON_FORECAST', 'https://maps.google.com/mapfiles/ms/micons/yellow-dot.png');
 121: 
 122: // 洪水情報がないときに表示するメッセージ
 123: define('NO_FLOOD_FORECAST', '現在,指定河川の洪水予報はありません.');
 124: 
 125: // SQLite DBファイル名:各自の環境に合わせて変更すること
 126: define('DBFILE', './riverFloodArea.sqlite3');
 127: 
 128: // SQLite テーブル名:浸水想定地区
 129: define('TABLE_AREA', 'area');
 130: 
 131: // 実行するSQL:浸水想定地区【変更不可】
 132: define('PRE_SELECT', 'SELECT * FROM ' . TABLE_AREA . ' WHERE id=:id');
 133: define('PRE_INSERT', 'INSERT INTO ' . TABLE_AREA . ' (id, area, latitude, longitude, premiere, latest) VALUES (:id, :area, :latitude, :longitude, :premiere, :latest)');
 134: define('PRE_UPDATE', 'UPDATE ' . TABLE_AREA . ' set area=:area, latitude=:latitude, longitude=:longitude, latest=:latest WHERE id=:id');
 135: 
 136: // 気象庁防災情報XML:高頻度フィード - 随時【変更不可】
 137: define('FEED', 'https://www.data.jma.go.jp/developer/xml/feed/extra.xml');
 138: 
 139: // 気象庁防災情報XML:長期フィード - 随時【変更不可】
 140: define('FEED_L', 'https://www.data.jma.go.jp/developer/xml/feed/extra_l.xml');
 141: 
 142: // 各種定数(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を取得

jmaRiverFloodForecast.php

 488: /**
 489:  * 気象庁防災情報XMLから指定河川洪水予報に関する情報URLを取得
 490:  * @param   array  $urls    URL格納配列
 491:  * @param   string $errmsg  エラーメッセージ格納用
 492:  * @return  bool TRUE:取得成功/FALSE:取得失敗
 493: */
 494: function jmaGetRiverFloodForecastURLs(&$urls, &$errmsg) {
 495:     // URLパターン
 496:     $vxko = '/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VXKO[0-9]+\_[0-9]+\.xml/ui';
 497: 
 498:     // 随時フィードの解析
 499:     $cnt = 0;
 500:     $pcc = new pahooCache(LIFE_CACHE_FEED, DIR_CACHE_FEED);
 501:     $xml = $pcc->simplexml_load(FEED);
 502:     // レスポンス・チェック
 503:     if ($pcc->iserror() || !isset($xml->entry)) {
 504:         $errmsg = '気象庁防災情報XMLにアクセスできません';
 505:         return FALSE;
 506:     }
 507:     foreach ($xml->entry as $node) {
 508:         // URLを取得
 509:         if (preg_match($vxko, $node->id, $arr> 0) {
 510:             $urls[$cnt] = $arr[0];
 511:             $cnt++;
 512:         }
 513:     }
 514:     $pcc = NULL;
 515: 
 516:     // 長期フィードの解析
 517:     $pcc = new pahooCache(LIFE_CACHE_FEED_L, DIR_CACHE_FEED_L);
 518:     $xml = $pcc->simplexml_load(FEED_L);
 519:     // レスポンス・チェック
 520:     if ($pcc->iserror() || !isset($xml->entry)) {
 521:         $errmsg = '気象庁防災情報XMLにアクセスできません';
 522:         return FALSE;
 523:     }
 524:     foreach ($xml->entry as $node) {
 525:         // URLを取得
 526:         if (preg_match($vxko, $node->id, $arr> 0) {
 527:             if (array_search($arr[0], $urls) === FALSE) {
 528:                 $urls[$cnt] = $arr[0];
 529:                 $cnt++;
 530:             }
 531:         }
 532:     }
 533:     $pcc = NULL;
 534: 
 535:     // エラー・チェック
 536: //  if ($cnt == 0) {
 537: //      $errmsg = '指定河川洪水予報はありません';
 538: //      return FALSE;
 539: //  }
 540: 
 541:     // URLを日時の新しい順にソート
 542:     if (count($urls> 0)   rsort($urls);
 543: 
 544:     return TRUE;
 545: }

前述のフィードから、指定河川洪水予報に関する情報URLを取得するユーザー関数が jmaGetRiverFloodForecastURLs である。
VXKO を含むURLを配列 $urls に格納していき、最後に配列 $urls を大きい順にソートすることで、日付の新しい順にソートしたことになる。

解説:指定河川洪水予報情報の取り出し

指定河川洪水予報情報の取り出し
指定河川洪水予報情報に関する情報XML VXKO から、発表日時、主文、洪水想定地区、雨量を取り出し、配列 $items に格納する。

VXKO からは洪水想定地区の緯度・経度が分からないため、pahooGeoCodeクラスのメソッド searchPoint3 を使って緯度・経度を取得する。
このメソッドは、WebAPIを呼び出して住所から緯度・経度を取得するものだが、いちいち呼び出すと時間がかかる(GoogleAPIは費用もかかる)ので、一度検索したデータはデータベース SQLite に格納しておき、二度目からはDBのデータを取り出すようにした。

jmaRiverFloodForecast.php

 547: /**
 548:  * 指定河川洪水予報情報を取得(気象庁防災情報XMLから)
 549:  * @param   object $pgc     pahooGeoCodeオブジェクト
 550:  * @param   array  $items   指定河川洪水予報を格納する配列
 551:  * @param   string $urls    情報XMLのURLを格納する配列
 552:  * @param   string $errmsg  エラーメッセージ格納用
 553:  * @return  bool TRUE:取得成功/FALSE:失敗
 554: */
 555: function getRiverFloodForecast($pgc, &$items, &$urls, &$errmsg) {
 556:     // マッチングパターン
 557:     $pat1 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:([0-9]+)/ui';      // 年月日時分
 558: 
 559:     // オブジェクト生成
 560:     $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA);
 561:     $pgc = new pahooGeoCode();
 562: 
 563:     // DBオープン
 564:     $pdo = new PDO('sqlite:' . DBFILE);
 565: 
 566:     // 指定河川洪水予報取に関する情報URLを取得
 567:     $urls = array('');
 568:     jmaGetRiverFloodForecastURLs($urls, $errmsg);
 569:     if ($errmsg !'')      return FALSE;
 570:     if (count($urls) == 0)  return TRUE;
 571: 
 572:     $count = 1;
 573:     foreach ($urls as $key=>$vxko) {
 574:         // 指定河川洪水予報の取得
 575:         $xml = $pcc->simplexml_load($vxko);
 576:         if ($xml == FALSE)  continue;           // ver.1.22
 577: 
 578:         // レスポンス・チェック
 579:         if ($pcc->iserror() || !isset($xml->Body->Warning->Item)) {
 580:             $errmsg = '気象庁防災情報XMLから指定河川洪水予報を取得できません';
 581:             return FALSE;
 582:         }
 583:         // 発表日時
 584:         $dt = (string)$xml->Head->ReportDateTime;
 585:         // 古い情報かどうか
 586:         if (time() - strtotime($dt> INTERVAL * 60 * 60) {
 587:             continue;
 588:         }
 589:         $items[$count]['dt'] = $dt;
 590:         preg_match($pat1, $dt, $arr);
 591:         $items[$count]['year']    = (int)$arr[1];
 592:         $items[$count]['month']   = (int)$arr[2];
 593:         $items[$count]['day']     = (int)$arr[3];
 594:         $items[$count]['hour']    = (int)$arr[4];
 595:         $items[$count]['minuite'] = (int)$arr[5];
 596:         // 指定河川洪水予報
 597:         $data = array();
 598:         foreach ($xml->Body->Warning->Item as $item) {
 599:             // 主文
 600:             if (isset($item->Kind->Property->Type&& ((string)$item->Kind->Property->Type == '主文')) {
 601:                 if (isset($items[$count]['main'])) {
 602:                     $items[$count]['main'."\n" . (string)$item->Kind->Property->Text;
 603:                 } else {
 604:                     $items[$count]['main'] = (string)$item->Kind->Property->Text;
 605:                 }
 606:             // 浸水想定地区
 607:             } else if (isset($item->Kind->Property->Type&& ((string)$item->Kind->Property->Type == '浸水想定地区')) {
 608:                 $i = 1;
 609:                 foreach ($item->Areas->Area as $area) {
 610:                     $query = (string)$area->Prefecture . (string)$area->City;
 611:                     $items[$count]['areas'][$i]['area'] = $query;
 612:                     $id = (string)$area->CityCode;
 613:                     // 緯度・経度をDBから取得
 614:                     if (getAreaFromDB($pdo, $id, $data)) {
 615:                         $latitude  = $data['latitude'];
 616:                         $longitude = $data['longitude'];
 617:                     } else {
 618:                         list($n, $url) = $pgc->searchPoint3($query, GEOSERVICE, 'address');
 619:                         // エラーがなければ最初の住所を対象にする
 620:                         if (! $pgc->iserror()) {
 621:                             list($latitude, $longitude, $address) = $pgc->getPoint(1);
 622:                             // DB登録
 623:                             $data['area']      = $query;
 624:                             $data['latitude']  = $latitude;
 625:                             $data['longitude'] = $longitude;
 626:                             $res = storeAreaToDB($pdo, $id, $data);
 627:                             if (! $res) {
 628:                                 $errmsg = 'DB書き込みに失敗しました';
 629:                                 return FALSE;
 630:                             }
 631:                         } else {
 632:                             $errmsg = $pgc->geterror();
 633:                             return FALSE;
 634:                         }
 635:                     }
 636:                     $items[$count]['areas'][$i]['latitude']  = $latitude;
 637:                     $items[$count]['areas'][$i]['longitude'] = $longitude;
 638:                     $i++;
 639:                 }
 640:             }
 641:         }
 642:         // 雨量情報
 643:         if (isset($xml->Body->MeteorologicalInfos->MeteorologicalInfo->Item)) {
 644:             foreach ($xml->Body->MeteorologicalInfos->MeteorologicalInfo->Item as $item) {
 645:                 if (isset($item->Kind->Property->Type&& ((string)$item->Kind->Property->Type == '雨量')) {
 646:                     if (isset($items[$count]['rainfall'])) {
 647:                         $items[$count]['rainfall'."\n" . (string)$item->Kind->Property->Text;
 648:                     } else {
 649:                         $items[$count]['rainfall'] = (string)$item->Kind->Property->Text;
 650:                     }
 651:                 }
 652:             }
 653:         }
 654:         $count++;
 655:     }
 656: 
 657:     // DBクローズ
 658:     $pdo = NULL;
 659:     // オブジェクト解放
 660:     $pgc = $pcc = NULL;
 661: 
 662:     return TRUE;
 663: }

SQLite のデータベースは、メインプログラムの冒頭でユーザー関数 initDB を呼び出し、データベースファイル DBFILE が存在しなければ新規作成するようにしている。

jmaRiverFloodForecast.php

 278: /**
 279:  * DBの初期化
 280:  * @param   なし
 281:  * @return  bool TRUE/FALSE
 282: */
 283: function initDB() {
 284:     try {
 285:         $pdo = new PDO('sqlite:' . DBFILE);
 286:         $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 287: 
 288:         // テーブル作成:浸水想定地区
 289:         // id:観測所のCityCode, area:都道府県+市町村名
 290:         // latitude:緯度, longitude:経度
 291:         // premiere:登録日時, latest:更新日時
 292:         $pdo->exec('CREATE TABLE IF NOT EXISTS ' . TABLE_AREA . '(
 293:          id        INTEGER PRIMARY KEY AUTOINCREMENT,
 294:             area      TEXT,
 295:             latitude  REAL,
 296:             longitude REAL,
 297:             premiere  TEXT,
 298:             latest    TEXT
 299:         )');
 300:      $res = TRUE;
 301:     } catch (PDOException $e) {
 302:         $res = FALSE;
 303:     }
 304: 
 305:     return $res;
 306: }

解説:指定河川洪水予報情報をマッピング情報に変換

jmaRiverFloodForecast.php

 665: /**
 666:  * 指定河川洪水予報情報をマッピング情報に変換
 667:  * @param   array  $items   指定河川洪水予報情報を格納した配列
 668:  * @param   array  $points  マッピング情報を格納する配列
 669:  * @return  int 変換したマッピング情報の件数
 670: */
 671: function info2points(&$items, &$points) {
 672:     // データがない
 673:     if (count($items) == 0)     return 0;
 674: 
 675:     // 変換処理
 676:     $i = $j = 0;
 677:     $cnt = 1;
 678:     foreach ($items as $i=>$item) {
 679:         if ($cnt > MAX_MARKERS)     break;          // 最大プロット数
 680:         if (!isset($item['areas']))     continue;   // Ver.1.21修正
 681:         foreach ($item['areas'as $j=>$val) {
 682:             // 同じマッピング位置があるかどうか
 683:             $flag = FALSE;
 684:             for ($k = 1$k < $cnt$k++) {
 685:                 if ($val['area'] == $points[$k]['title']) {
 686:                     $flag = TRUE;
 687:                     break;
 688:                 }
 689:             }
 690:             // 新規のマッピング情報
 691:             if (! $flag) {
 692:                 // アイコン
 693:                 if (preg_match('/警戒レベル/ui', $items[$i]['main']) > 0) {
 694:                     $points[$cnt]['icon'] = ICON_CAUSION;
 695:                 } else {
 696:                     $points[$cnt]['icon'] = ICON_FORECAST;
 697:                 }
 698:                 // タイトル
 699:                 $points[$cnt]['title'] = $val['area'];
 700:                 // 緯度・経度
 701:                 $points[$cnt]['latitude']  = $items[$i]['areas'][$j]['latitude'];
 702:                 $points[$cnt]['longitude'] = $items[$i]['areas'][$j]['longitude'];
 703:                 // 情報ウィンドウの内容
 704:                 $points[$cnt]['description'] = 
 705:                     sprintf('%04d年%02d月%02d日 %02d時%02d分 発表<br /><span style="font-weight:bold;">%s</span><br />%s<br />%s',  $items[$i]['year'],
 706:                         $items[$i]['month'],
 707:                         $items[$i]['day'],
 708:                         $items[$i]['hour'],
 709:                         $items[$i]['minuite'],
 710:                         $val['area'],
 711:                         preg_replace('/\n/', '<br />', $items[$i]['main']),
 712:                         preg_replace('/\n/', '<br />', $items[$i]['rainfall']));
 713:                     $cnt++;
 714:             }
 715:         }
 716:     }
 717: 
 718:     return $cnt;
 719: }

getRiverFloodForecast で得た指定河川洪水予報情報を、マッピングしやすいように、洪水想定地区をキーに配列 $points に展開するユーザー関数が info2points である。

解説:地図描画について

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

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

マップで Leaflet を選択したとき、オーバーレイ地図を表示できるようにした。
詳しくは「PHPで最近の地震情報を表示する」をご覧いただきたい。

解説: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ライブラリ

jmaRiverFloodForecast.php

 183: <script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
 184: <script>
 185: function mytweet() {
 186:     $('#tweet').val('1');
 187:     document.myform.submit();
 188: }

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

jmaRiverFloodForecast.php

 857: <div id="{$target}" name="{$target}" style="width:{$width2}px;">
 858: <p>
 859: 🌊指定河川洪水予報情報 {$dt}現在
 860: &nbsp;{$tweet}{$bluesky}
 861: </p>
 862: {$html}
 863: </div>

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

jmaRiverFloodForecast.php

 422: /**
 423:  * HTMLオブジェクトの画像化
 424:  * @param   なし
 425:  * @return  string JavaScriptコード
 426: */
 427: function js_html2image() {
 428:     $target = TARGET;
 429:     $js = '';
 430: 
 431:     // Googleマップの場合
 432:     if (MAPSERVICE == 0) {
 433:         $js .=<<< EOT
 434: google.maps.event.addListener(map, 'tilesloaded', function() {
 435:     var capture = document.querySelector('#{$target}');
 436:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
 437:         var base64 = canvas.toDataURL('image/png');     // 画像化
 438:         $('#base64').val(base64);
 439:     });
 440: });
 441: 
 442: EOT;
 443: 
 444:     // Leafletの場合(ブラウザによってはうまく動作しない)
 445:     } else {
 446:         $js .=<<< EOT
 447: HTMLCanvasElement.prototype.getContext = function(origFn) {
 448:     return function(type, attribs) {
 449:         attribs = attribs || {};
 450:         attribs.preserveDrawingBuffer = true;
 451:         return origFn.call(this, type, attribs);
 452:     };
 453: } (HTMLCanvasElement.prototype.getContext);
 454: 
 455: // HTML画像化イベント登録
 456: function html2image() {
 457:     var capture = document.querySelector('#{$target}');
 458:     html2canvas(capture, {useCORS: true, allowTaint:true}).then(canvas => {
 459:         var base64 = canvas.toDataURL('image/png');     // 画像化
 460:         $('#base64').val(base64);
 461:     });
 462: };
 463: 
 464: // ズーム変更イベント
 465: map.on('zoomend', function() {
 466:     html2image();
 467: });
 468: 
 469: // マップ移動イベント
 470: map.on('moveend', function() {
 471:     html2image();
 472: });
 473: 
 474: // html2image()を発火させるために,ズームアウトして500msec後に元に戻す.
 475: var zoom = map.getZoom();
 476: map.setZoom(zoom - 1);
 477: setTimeout(function() {
 478:     map.setZoom(zoom);
 479: }, 500);
 480: 
 481: EOT;
 482:     }
 483: 
 484:     return $js;
 485: }

html2canvas を呼び出すタイミングだが、Googleマップの場合は tilesloadedイベントにフックする。
Leaflet の場合、これに相当するイベントがないため、ズーム変更完了イベントにフックし、強制的にズーム変更イベントを発生させるタイミングで画像化する。しかし、Leaflet では、画像を描画するレイヤ構造の関係で、html2canvas ライブラリでは位置ずれが起きることがわかっている。その場合は、Googleマップを選択してほしい。また、対策方法をご存じの方がいたらお知らせいただきたい
これらを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

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

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

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

jmaRiverFloodForecast.php

 370: /**
 371:  * Twitter(現・X)投稿
 372:  * @param   string $message 投稿文
 373:  * @param   string $res     応答メッセージ格納用
 374:  * @return  bool TRUE:成功/FALSE:失敗または未処理
 375: */
 376: function mediaTweet($message, &$res) {
 377:     if (! TWITTER)  return FALSE;
 378: 
 379:     $ret = TRUE;
 380:     if (isset($_POST['base64']) && ($_POST['base64'!'')) {
 381:         $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
 382:         $raws = array(base64_decode($base64));
 383:         $ptw = new pahooTwitterAPI();
 384:         $ptw->tweet_media_raw($message, $raws);
 385:         $errmsg = $ptw->errmsg;
 386:         $ret = ! $ptw->error;
 387:         $ptw = NULL;
 388:         if ($ret) {
 389:             $res = 'ツイートしました';
 390:         }
 391:     }
 392:     return $ret;
 393: }

メッセージと画像を Twitter(現・X)に投稿するのはサーバ側のPHPユーザー関数 mediaTweet である。
ブラウザからPOSTで受け取った画像データ $_POST['base64'] は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へ投稿する

jmaRiverFloodForecast.php

 395: /**
 396:  * Bluesky投稿
 397:  * @param   string $message 投稿文
 398:  * @param   string $res     応答メッセージ格納用
 399:  * @return  bool TRUE:成功/FALSE:失敗または未処理
 400: */
 401: function mediaBluesky($message, &$res) {
 402:     if (! BLUESKY)  return FALSE;
 403: 
 404:     $ret = TRUE;
 405:     if (isset($_POST['base64']) && ($_POST['base64'!'')) {
 406:         $base64 = preg_replace('/data\:image\/png\;base64\,/ui', '', $_POST['base64']);
 407:         $raws = array(base64_decode($base64));
 408:         $pbs = new pahooBlueskyAPI(BLUESKY_DOMAIN);
 409:         $res = $pbs->createSession();
 410:         $pbs->post($message, FALSE, NULL, NULL, $raws);
 411:         $errmsg = $pbs->geterror();
 412:         $ret = ! $pbs->iserror();
 413:         $res = $pbs->deleteSession();
 414:         $pbs = NULL;
 415:         if ($ret) {
 416:             $res = 'Blueskyに投稿しました';
 417:         }
 418:     }
 419:     return $ret;
 420: }

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

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

jmaRiverFloodForecast.php

 916: // Twitter(現・X)、Blueskyへ投稿する.
 917: $dt = nowDT();
 918: $message =<<< EOT
 919: 🌊指定河川洪水予報情報 {$dt}現在
 920: 
 921: (ご参考)PHPで指定河川洪水予報情報を表示する https://www.pahoo.org/e-soul/webtech/php05/php05-19-01.shtm #洪水
 922: 
 923: EOT;
 924: if (TWITTER && isButton('tweet')) {
 925:     mediaTweet($message, $res);
 926: }
 927: if (BLUESKY && isButton('bluesky')) {
 928:     mediaBluesky($message, $res);
 929: }
 930: 
 931: // 表示コンテンツを作成する.

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

活用例

みんなの知識 ちょっと便利帳」では、「指定河川洪水予報を地図上に表示」で本プログラムを利用し、見やすいページを提供している。ありがとうございます。

参考サイト

(この項おわり)
header