サンプル・プログラムの実行例
目次
サンプル・プログラム
jmaRiverFloodForecast.php | サンプル・プログラム本体。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
pahooCache.php | キャッシュ処理に関わるクラス pahooCache。 キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。 |
pahooTwitterAPI.php | Twitter APIを利用するクラス pahooTwitterAPI。 使い方は「PHPでTwitterに投稿(ツイート)する」などを参照。include_path が通ったディレクトリに配置すること。 |
バージョン | 更新日 | 内容 |
---|---|---|
1.2.3 | 2023/09/20 | js_html2image()--Leaflet用html2image()発火プロセス見直し |
1.22 | 2022/10/08 | getRiverFloodForecast() - 予報がないときの処理追加 |
1.21 | 2022/08/04 | info2points() - areasが無いデータをスキップ |
1.2 | 2022/07/17 | 最大プロット数をMAX_MARKERSに定義 |
1.1 | 2022/07/09 | 指定河川洪水予報が無いときのチェックロジック改良 |
バージョン | 更新日 | 内容 |
---|---|---|
6.3.1 | 2023/07/09 | bug-fix |
6.3.0 | 2023/07/02 | getPointsGSI()追加 |
6.2.0 | 2023/07/02 | ip2address()追加 |
6.1.0 | 2022/12/30 | ip2address()追加 |
6.0.4 | 2022/12/13 | PHP8.2対応 |
バージョン | 更新日 | 内容 |
---|---|---|
1.1.1 | 2023/02/11 | コメント追記 |
1.1 | 2021/04/08 | simplexml_load()メソッド追加 |
1.0 | 2021/04/02 | 初版 |
バージョン | 更新日 | 内容 |
---|---|---|
5.2.0 | 2023/07/17 | oembed() v2対応 |
5.1.0 | 2023/07/16 | extractMediaURL() -- file:///形式に対応 |
5.0.0 | 2023/07/02 | メソッドをTwitter API v2へ移行;v1.1は別名or廃止 |
4.9.0 | 2023/04/15 | tweet3() 追加 |
4.8.0 | 2023/01/28 | tweet2(),twitter_strcut2(),extractMediaURL()追加 |
気象庁防災情報XMLフォーマット
https://www.data.jma.go.jp/developer/xml/data/yyyymmddhhmmss_番号_電文コード_連番.xml指定河川の洪水予報に関する情報は、長期フィードの随時の中にある電文コード VXKO に入っている。そこで、フィードから VXKO を含むURLを取り出せば、それが目指す情報XMLとなる。
VXKOの構造
ここから必要な情報を取り出す。
準備:pahooGeoCode クラス
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の登録方法」を参照されたい。
準備:地図サービスの選択
観測所の都道府県・市町村名からマッピング位置を特定するための住所検索サービスは、Google、Yahoo!JAPAN、HeartRails Geo API、OSM Nominatim Search API から選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
準備:キャッシュ・システム
67: //キャッシュ保持時間(分) 0:キャッシュしない
68: //気象庁へのアクセス負荷軽減のため,適切な時間を設定してください.
69: define('LIFE_CACHE_FEED', 5); //高頻度 - 随時フィードに対して
70: define('LIFE_CACHE_FEED_L', 120); //長期 - 随時フィードに対して
71: define('LIFE_CACHE_DATA', 720); //指定河川洪水予報情報に対して
72:
73: //キャッシュ・ディレクトリ
74: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
75: //天気予報系のプログラムとは別のディレクトリを指定してください.
76: define('DIR_CACHE_FEED', './pcache1/');
77: define('DIR_CACHE_FEED_L', './pcache2/');
78: define('DIR_CACHE_DATA', './pcache3/');
キャッシュ保持時間、キャッシュ・ディレクトリともに、自サイトの環境に応じて変更してほしい。天気予報系プログラムと別のキャッシュ・ディレクトリにした方が、お互いのキャッシュ保持時間の干渉を受けなくなる。
配布ファイルは、新しい地震情報が入ってくることを考え、フィードの方を短く、指定河川洪水予報情報の方は長くキャッシュ保持時間を設定してある。
準備:各種定数
52: //マップの表示サイズ(単位:ピクセル)
53: define('MAP_WIDTH', 640);
54: define('MAP_HEIGHT', 480);
55: //マップID
56: define('MAPID', 'map_id');
57: //初期値
58: define('DEF_LATITUDE', 38.82); //緯度
59: define('DEF_LONGITUDE', 138.28); //経度
60: define('DEF_ZOOM', 5); //ズーム
61: define('DEF_TYPE', 'ROADMAP'); //マップタイプ
62: define('DEF_OVERLAYS', 'GSIELEV,GSIFAULT'); //オーバーレイ(Leaflet使用時のみ)
63: define('INFO_WIDTH', (MAP_WIDTH * 0.75)); //情報ウィンドウの最大幅
64: define('INFO_OFFSET_X', 0); //情報ウィンドウのオフセット位置(X)
65: define('INFO_OFFSET_Y', -25); //情報ウィンドウのオフセット位置(Y)
66:
67: //キャッシュ保持時間(分) 0:キャッシュしない
68: //気象庁へのアクセス負荷軽減のため,適切な時間を設定してください.
69: define('LIFE_CACHE_FEED', 5); //高頻度 - 随時フィードに対して
70: define('LIFE_CACHE_FEED_L', 120); //長期 - 随時フィードに対して
71: define('LIFE_CACHE_DATA', 720); //指定河川洪水予報情報に対して
72:
73: //キャッシュ・ディレクトリ
74: //書き込み可能で,外部からアクセスされないディレクトリを指定してください.
75: //天気予報系のプログラムとは別のディレクトリを指定してください.
76: define('DIR_CACHE_FEED', './pcache1/');
77: define('DIR_CACHE_FEED_L', './pcache2/');
78: define('DIR_CACHE_DATA', './pcache3/');
79:
80: //これより古い情報は無視する(単位:時間)
81: define('INTERVAL', 24);
82:
83: //最大プロット数
84: define('MAX_MARKERS', 999);
85:
86: //警戒レベルのアイコン
87: define('ICON_CAUSION', 'https://maps.google.com/mapfiles/ms/micons/red-dot.png');
88: //予報アイコン
89: define('ICON_FORECAST', 'https://maps.google.com/mapfiles/ms/micons/yellow-dot.png');
90:
91: //SQLite DBファイル名:各自の環境に合わせて変更すること
92: define('DBFILE', './riverFloodArea.sqlite3');
93:
94: //SQLite テーブル名:浸水想定地区
95: define('TABLE_AREA', 'area');
96:
97: //実行するSQL:浸水想定地区【変更不可】
98: define('PRE_SELECT', 'SELECT * FROM ' . TABLE_AREA . ' WHERE id=:id');
99: define('PRE_INSERT', 'INSERT INTO ' . TABLE_AREA . ' (id, area, latitude, longitude, premiere, latest) VALUES (:id, :area, :latitude, :longitude, :premiere, :latest)');
100: define('PRE_UPDATE', 'UPDATE ' . TABLE_AREA . ' set area=:area, latitude=:latitude, longitude=:longitude, latest=:latest WHERE id=:id');
101:
102: //気象庁防災情報XML:高頻度フィード - 随時【変更不可】
103: define('FEED', 'https://www.data.jma.go.jp/developer/xml/feed/extra.xml');
104:
105: //気象庁防災情報XML:長期フィード - 随時【変更不可】
106: define('FEED_L', 'https://www.data.jma.go.jp/developer/xml/feed/extra_l.xml');
107:
108: //住所・緯度・経度に関わるクラス:include_pathが通ったディレクトリに配置
109: require_once('pahooGeoCode.php');
110:
111: //キャッシュ処理に関わるクラス:include_pathが通ったディレクトリに配置
112: require_once('pahooCache.php');
113:
114: //Twitterクラス:include_pathが通ったディレクトリに配置
115: if (TWITTER) {
116: require_once('pahooTwitterAPI.php');
117: }
解説:気象庁防災情報XMLから指定河川洪水予報に関する情報URLを取得
449: /**
450: * 気象庁防災情報XMLから指定河川洪水予報に関する情報URLを取得
451: * @param array $urls URL格納配列
452: * @param string $errmsg エラーメッセージ格納用
453: * @return bool TRUE:取得成功/FALSE:取得失敗
454: */
455: function jmaGetRiverFloodForecastURLs(&$urls, &$errmsg) {
456: //URLパターン
457: $vxko = '/https?\:\/\/www\.data\.jma\.go\.jp\/developer\/xml\/data\/([0-9\_]+)VXKO[0-9]+\_[0-9]+\.xml/ui';
458:
459: //随時フィードの解析
460: $cnt = 0;
461: $pcc = new pahooCache(LIFE_CACHE_FEED, DIR_CACHE_FEED);
462: $xml = $pcc->simplexml_load(FEED);
463: //レスポンス・チェック
464: if ($pcc->iserror() || !isset($xml->entry)) {
465: $errmsg = '気象庁防災情報XMLにアクセスできません';
466: return FALSE;
467: }
468: foreach ($xml->entry as $node) {
469: //URLを取得
470: if (preg_match($vxko, $node->id, $arr) > 0) {
471: $urls[$cnt] = $arr[0];
472: $cnt++;
473: }
474: }
475: $pcc = NULL;
476:
477: //長期フィードの解析
478: $pcc = new pahooCache(LIFE_CACHE_FEED_L, DIR_CACHE_FEED_L);
479: $xml = $pcc->simplexml_load(FEED_L);
480: //レスポンス・チェック
481: if ($pcc->iserror() || !isset($xml->entry)) {
482: $errmsg = '気象庁防災情報XMLにアクセスできません';
483: return FALSE;
484: }
485: foreach ($xml->entry as $node) {
486: //URLを取得
487: if (preg_match($vxko, $node->id, $arr) > 0) {
488: if (array_search($arr[0], $urls) === FALSE) {
489: $urls[$cnt] = $arr[0];
490: $cnt++;
491: }
492: }
493: }
494: $pcc = NULL;
495:
496: //エラー・チェック
497: // if ($cnt == 0) {
498: // $errmsg = '指定河川洪水予報はありません';
499: // return FALSE;
500: // }
501:
502: //URLを日時の新しい順にソート
503: if (count($urls) > 0) rsort($urls);
504:
505: return TRUE;
506: }
VXKO を含むURLを配列 $urls に格納していき、最後に配列 $urls を大きい順にソートすることで、日付の新しい順にソートしたことになる。
解説:指定河川洪水予報情報の取り出し
VXKO からは洪水想定地区の緯度・経度が分からないため、pahooGeoCodeクラスのメソッド searchPoint3 を使って緯度・経度を取得する。
このメソッドは、WebAPIを呼び出して住所から緯度・経度を取得するものだが、いちいち呼び出すと時間がかかる(GoogleAPIは費用もかかる)ので、一度検索したデータはデータベース SQLite に格納しておき、二度目からはDBのデータを取り出すようにした。
508: /**
509: * 指定河川洪水予報情報を取得(気象庁防災情報XMLから)
510: * @param object $pgc pahooGeoCodeオブジェクト
511: * @param array $items 指定河川洪水予報を格納する配列
512: * @param string $urls 情報XMLのURLを格納する配列
513: * @param string $errmsg エラーメッセージ格納用
514: * @return bool TRUE:取得成功/FALSE:失敗
515: */
516: function getRiverFloodForecast($pgc, &$items, &$urls, &$errmsg) {
517: //マッチングパターン
518: $pat1 = '/([0-9]+)\-([0-9]+)\-([0-9]+)T([0-9]+)\:([0-9]+)/ui'; //年月日時分
519:
520: //オブジェクト生成
521: $pcc = new pahooCache(LIFE_CACHE_DATA, DIR_CACHE_DATA);
522: $pgc = new pahooGeoCode();
523:
524: //DBオープン
525: $pdo = new PDO('sqlite:' . DBFILE);
526:
527: //指定河川洪水予報取に関する情報URLを取得
528: $urls = array('');
529: jmaGetRiverFloodForecastURLs($urls, $errmsg);
530: if ($errmsg != '') return FALSE;
531: if (count($urls) == 0) return TRUE;
532:
533: $count = 1;
534: foreach ($urls as $key=>$vxko) {
535: //指定河川洪水予報の取得
536: $xml = $pcc->simplexml_load($vxko);
537: if ($xml == FALSE) continue; //ver.1.22
538:
539: //レスポンス・チェック
540: if ($pcc->iserror() || !isset($xml->Body->Warning->Item)) {
541: $errmsg = '気象庁防災情報XMLから指定河川洪水予報を取得できません';
542: return FALSE;
543: }
544: //発表日時
545: $dt = (string)$xml->Head->ReportDateTime;
546: //古い情報かどうか
547: if (time() - strtotime($dt) > INTERVAL * 60 * 60) {
548: continue;
549: }
550: $items[$count]['dt'] = $dt;
551: preg_match($pat1, $dt, $arr);
552: $items[$count]['year'] = (int)$arr[1];
553: $items[$count]['month'] = (int)$arr[2];
554: $items[$count]['day'] = (int)$arr[3];
555: $items[$count]['hour'] = (int)$arr[4];
556: $items[$count]['minuite'] = (int)$arr[5];
557: //指定河川洪水予報
558: $data = array();
559: foreach ($xml->Body->Warning->Item as $item) {
560: //主文
561: if (isset($item->Kind->Property->Type) && ((string)$item->Kind->Property->Type == '主文')) {
562: if (isset($items[$count]['main'])) {
563: $items[$count]['main'] .= "\n" . (string)$item->Kind->Property->Text;
564: } else {
565: $items[$count]['main'] = (string)$item->Kind->Property->Text;
566: }
567: //浸水想定地区
568: } else if (isset($item->Kind->Property->Type) && ((string)$item->Kind->Property->Type == '浸水想定地区')) {
569: $i = 1;
570: foreach ($item->Areas->Area as $area) {
571: $query = (string)$area->Prefecture . (string)$area->City;
572: $items[$count]['areas'][$i]['area'] = $query;
573: $id = (string)$area->CityCode;
574: //緯度・経度をDBから取得
575: if (getAreaFromDB($pdo, $id, $data)) {
576: $latitude = $data['latitude'];
577: $longitude = $data['longitude'];
578: } else {
579: list($n, $url) = $pgc->searchPoint3($query, GEOSERVICE, 'address');
580: //エラーがなければ最初の住所を対象にする
581: if (! $pgc->iserror()) {
582: list($latitude, $longitude, $address) = $pgc->getPoint(1);
583: //DB登録
584: $data['area'] = $query;
585: $data['latitude'] = $latitude;
586: $data['longitude'] = $longitude;
587: $res = storeAreaToDB($pdo, $id, $data);
588: if (! $res) {
589: $errmsg = 'DB書き込みに失敗しました';
590: return FALSE;
591: }
592: } else {
593: $errmsg = $pgc->geterror();
594: return FALSE;
595: }
596: }
597: $items[$count]['areas'][$i]['latitude'] = $latitude;
598: $items[$count]['areas'][$i]['longitude'] = $longitude;
599: $i++;
600: }
601: }
602: }
603: //雨量情報
604: if (isset($xml->Body->MeteorologicalInfos->MeteorologicalInfo->Item)) {
605: foreach ($xml->Body->MeteorologicalInfos->MeteorologicalInfo->Item as $item) {
606: if (isset($item->Kind->Property->Type) && ((string)$item->Kind->Property->Type == '雨量')) {
607: if (isset($items[$count]['rainfall'])) {
608: $items[$count]['rainfall'] .= "\n" . (string)$item->Kind->Property->Text;
609: } else {
610: $items[$count]['rainfall'] = (string)$item->Kind->Property->Text;
611: }
612: }
613: }
614: }
615: $count++;
616: }
617:
618: //DBクローズ
619: $pdo = NULL;
620: //オブジェクト解放
621: $pgc = $pcc = NULL;
622:
623: return TRUE;
624: }
266: /**
267: * DBの初期化
268: * @param なし
269: * @return bool TRUE/FALSE
270: */
271: function initDB() {
272: try {
273: $pdo = new PDO('sqlite:' . DBFILE);
274: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
275:
276: //テーブル作成:浸水想定地区
277: //id:観測所のCityCode, area:都道府県+市町村名
278: //latitude:緯度, longitude:経度
279: //premiere:登録日時, latest:更新日時
280: $pdo->exec('CREATE TABLE IF NOT EXISTS ' . TABLE_AREA . '(
281: id INTEGER PRIMARY KEY AUTOINCREMENT,
282: area TEXT,
283: latitude REAL,
284: longitude REAL,
285: premiere TEXT,
286: latest TEXT
287: )');
288: $res = TRUE;
289: } catch (PDOException $e) {
290: $res = FALSE;
291: }
292:
293: return $res;
294: }
解説:指定河川洪水予報情報をマッピング情報に変換
626: /**
627: * 指定河川洪水予報情報をマッピング情報に変換
628: * @param array $items 指定河川洪水予報情報を格納した配列
629: * @param array $points マッピング情報を格納する配列
630: * @return int 変換したマッピング情報の件数
631: */
632: function info2points(&$items, &$points) {
633: //データがない
634: if (count($items) == 0) return 0;
635:
636: //変換処理
637: $i = $j = 0;
638: $cnt = 1;
639: foreach ($items as $i=>$item) {
640: if ($cnt > MAX_MARKERS) break; //最大プロット数
641: if (!isset($item['areas'])) continue; //Ver.1.21修正
642: foreach ($item['areas'] as $j=>$val) {
643: //同じマッピング位置があるかどうか
644: $flag = FALSE;
645: for ($k = 1; $k < $cnt; $k++) {
646: if ($val['area'] == $points[$k]['title']) {
647: $flag = TRUE;
648: break;
649: }
650: }
651: //新規のマッピング情報
652: if (! $flag) {
653: //アイコン
654: if (preg_match('/警戒レベル/ui', $items[$i]['main']) > 0) {
655: $points[$cnt]['icon'] = ICON_CAUSION;
656: } else {
657: $points[$cnt]['icon'] = ICON_FORECAST;
658: }
659: //タイトル
660: $points[$cnt]['title'] = $val['area'];
661: //緯度・経度
662: $points[$cnt]['latitude'] = $items[$i]['areas'][$j]['latitude'];
663: $points[$cnt]['longitude'] = $items[$i]['areas'][$j]['longitude'];
664: //情報ウィンドウの内容
665: $points[$cnt]['description'] =
666: sprintf('%04d年%02d月%02d日 %02d時%02d分 発表<br /><span style="font-weight:bold;">%s</span><br />%s<br />%s', $items[$i]['year'],
667: $items[$i]['month'],
668: $items[$i]['day'],
669: $items[$i]['hour'],
670: $items[$i]['minuite'],
671: $val['area'],
672: preg_replace('/\n/', '<br />', $items[$i]['main']),
673: preg_replace('/\n/', '<br />', $items[$i]['rainfall']));
674: $cnt++;
675: }
676: }
677: }
678:
679: return $cnt;
680: }
解説:地図描画について
解説:オーバーレイ表示(Leaflet選択時のみ)
解説:ツイート機能
活用例
参考サイト
- WebAPIの登録方法:ぱふぅ家のホームページ
- PHPで最近の地震情報を表示する:ぱふぅ家のホームページ
- PHPで住所・ランドマークから最寄り駅を求める:ぱふぅ家のホームページ
- PHPで緯度・経度から住所を求める:ぱふぅ家のホームページ
- JavaScriptでHTML表示を画像保存する:ぱふぅ家のホームページ
- HTMLとCSSでさまざまなアイコンを表示する:ぱふぅ家のホームページ
- Twitter API - WebAPIの登録方法:ぱふぅ家のホームページ
- PHPでTwitterに画像付きメッセージ投稿:ぱふぅ家のホームページ
- 指定河川洪水予報を地図上に表示:みんなの知識 ちょっと便利帳
表示マップの種類を変えたり、マップを含めてツイートすることができる。
(2023年9月20日)js_html2image()--Leaflet用html2image()発火プロセス見直し
(2022年10月8日)予報がないときの処理追加