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

サンプル・プログラム
floodMap.php | サンプル・プログラム本体。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」などを参照のこと。include_pathが通ったディレクトリに配置する。 |
プログラムの方針
この一覧表の実体ページから、各々の観測所について
- 観測所 ID
- 観測所名
- 観測所 URL
- 河川名
- 水位
- 観測時刻
- 基準水位(4 段階)

一覧表にある観測所の所在地は市町村名までのため、このままでは正確な場所をマッピングできない。そこで、観測所 URL を開き、そこに表示される所在地を使って、Google Geocoding API を呼び出し、位置情報(緯度・経度)を取得する。

台風などで基準を超えた観測地点情報が多くなると、いちいち Google Geocoding API を呼び出して位置情報を取得するのに時間がかかる。そこで、一度取得した位置情報は、サーバのデータベースに格納し、次からはデータベース上の位置情報を参照するようにする。DBMS は、PHP からインストール不要で使える SQLite を使うことにする。

解説:準備
0035: //河川の水位が氾濫注意水位以上の地点URL
0036: //define('RIVER_URL', 'http://www.river.go.jp/kawabou/overOut/stageOverP1.html');
0037: define('RIVER_URL', 'https://www.river.go.jp/kawabou/html/list/4/ipSuiiChouka_ac80_fw0.html'); //v.1.02
0038:
0039: //SQLite DBファイル名:各自の環境に合わせて変更すること
0040: define('DBFILE', './flodMapBD.sqlite3');
0041:
0042: //SQLite テーブル名:観測地点情報
0043: define('TABLE_OBPOINT', 'obpoint');
0044:
0045: //実行するSQL:観測地点情報
0046: define('PRE_SELECT', 'SELECT * FROM ' . TABLE_OBPOINT . ' WHERE id=:id');
0047: define('PRE_INSERT', 'INSERT INTO ' . TABLE_OBPOINT . ' (id, title, address, latitude, longitude, premiere, latest) VALUES (:id, :title, :address, :latitude, :longitude, :premiere, :latest)');
0048: define('PRE_UPDATE', 'UPDATE ' . TABLE_OBPOINT . ' set title=:title, address=:address, latitude=:latitude, longitude=:longitude, latest=:latest WHERE id=:id');
0049:
0050: //マップ・アイコン
0051: define('GICON_LEVEL1', 'https://maps.google.com/mapfiles/ms/micons/green-dot.png'); //水防団待機
0052: define('GICON_LEVEL2', 'https://maps.google.com/mapfiles/ms/micons/yellow-dot.png'); //氾濫注意
0053: define('GICON_LEVEL3', 'https://maps.google.com/mapfiles/ms/micons/orange-dot.png'); //避難判断
0054: define('GICON_LEVEL4', 'https://maps.google.com/mapfiles/ms/micons/red-dot.png'); //氾濫危険

SQLite のデータベース実体ファイルは定数 DBFILE に定義する。PHP から書き込み可能なファイルであれば、フォルダやファイル名は自由に設定してかまわない。
本プログラム中のユーザー関数 initDB によってデータベースを自動生成するので、事前準備は不用である。

GoogleMaps の表示サイズ、マップ ID、マップ・アイコンは自由に変更することができる。
準備:pahooGeoCode クラス
0037: class pahooGeoCode {
0038: var $items; //検索結果格納用
0039: var $error; //エラーフラグ
0040: var $hits; //検索ヒット件数
0041: var $webapi; //直前に呼び出したWebAPI URL
0042:
0043: //Google Cloud Platform APIキー
0044: //https://cloud.google.com/maps-platform/
0045: //※Google Maps APIを利用しないのなら登録不要
0046: var $GOOGLE_API_KEY_1 = '**************************'; //HTTPリファラ用
0047: var $GOOGLE_API_KEY_2 = '**************************'; //IP制限用
0048:
0049: //Yahoo! JAPAN Webサービス アプリケーションID
0050: //https://e.developer.yahoo.co.jp/register
0051: //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
0052: var $YAHOO_APPLICATION_ID = '*****************************';
クラスについては「PHP でクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図として Google マップを利用するのであれば、Google Cloud Platform API キー が必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を、YOLP マップを利用するのであれば、Yahoo! JAPAN Web サービス アプリケーション IDが必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を、それぞれ参照されたい。
準備:地図サービス(WebAPI)の選択
0056: //地図描画サービスの選択
0057: // 0:Google
0058: // 2:地理院地図・OSM
0059: define('MAPSERVICE', 2);
0060:
0061: //マップの表示サイズ(単位:ピクセル)
0062: define('MAP_WIDTH', 600);
0063: define('MAP_HEIGHT', 600);
0064: //マップID
0065: define('MAPID', 'map_id');
0066: //初期値
0067: define('DEF_TYPE', 'roadmap'); //マップタイプ
0068: define('DEF_LATITUDE', 38.671); //緯度
0069: define('DEF_LONGITUDE', 139.779); //経度
0070: define('DEF_ZOOM', 5); //ズーム
解説:河川情報の取り出し
0275: /**
0276: * 国交省「川の防災情報」をスクレイピングし
0277: * 観測地点情報と氾濫注意情報を配列に格納
0278: * @param string $url スクレイピングするURL
0279: * @param array $items 観測地点情報を格納する配列
0280: * @return int 取得情報件数/FALSE:失敗
0281: */
0282: function getRiverInfo($url, &$items) {
0283: static $pat1 = "/<td\snowrap\swidth\=\"80\"\sclass\=\"tb1td1\"/iu";
0284: static $pat2 = "/'([0-9]+)','([0-9|\-]+)'/iu";
0285: static $pat3 = "/<td\snowrap\swidth\=\"79\"\sclass\=\"tb1td2\"/iu";
0286:
0287: $infp =fopen($url, 'r');
0288: if ($infp == FALSE) return FALSE;
0289:
0290: $cnt = 1;
0291: $flag = FALSE;
0292: while (! feof($infp)) {
0293: $str = fgets($infp);
0294: //観測地点
0295: if (preg_match($pat1, $str) > 0) {
0296: $str = fgets($infp);
0297: if (preg_match($pat2, $str, $arr) > 0) {
0298: $id = $arr[1];
0299: $items[$cnt]['id'] = $id;
0300: $items[$cnt]['title'] = trim(fgets($infp));
0301: //DB検索
0302: if (getOBpointFromDB($id, $items[$cnt]) == FALSE) {
0303: //GoogleジオコーディングAPI検索
0304: $items[$cnt]['url'] = "https://www.river.go.jp/kawabou/ipSuiiKobetu.do?init=init&obsrvId={$arr[1]}&gamenId={$arr[2]}&timeType=60&requestType=1&fldCtlParty=no";
0305: if (($res = getAddress($items[$cnt]['url'])) != FALSE) {
0306: $items[$cnt]['address'] = $res;
0307: if (getLatLong($items[$cnt]) != FALSE) {
0308: storeOBpointToDB($id, $items[$cnt]);
0309: }
0310: }
0311: }
0312: $flag = TRUE;
0313: }
0314: } else if ($flag && preg_match($pat3, $str) > 0) {
0315: //河川名
0316: $str = fgets($infp);
0317: $items[$cnt]['river'] = trim(strip_tags($str));
0318: //水位
0319: $str = fgets($infp);
0320: $items[$cnt]['level'] = trim(strip_tags($str));
0321: //観測時刻
0322: $str = fgets($infp);
0323: $str = fgets($infp);
0324: $items[$cnt]['time'] = trim(strip_tags($str));
0325: //警戒水位
0326: $str = fgets($infp);
0327: $items[$cnt]['causion1'] = trim(strip_tags($str));
0328: $str = fgets($infp);
0329: $items[$cnt]['causion2'] = trim(strip_tags($str));
0330: $str = fgets($infp);
0331: $items[$cnt]['causion3'] = trim(strip_tags($str));
0332: $str = fgets($infp);
0333: $items[$cnt]['causion4'] = trim(strip_tags($str));
0334: $cnt++;
0335: $flag = FALSE;
0336: }
0337: }
0338: fclose($infp);
0339:
0340: return ($cnt - 1);
0341: }
観測所のリンク先を開く JavaScript関数 openKobetu の第一引数がユニーク数であるようなので、これを観測所 ID とみなす。

観測所 ID をキーにユーザー関数 getOBpointFromDB で DB 検索を行い、ヒットしたら取得した位置情報を $items に格納し、ヒットしなかったら観測所のリンク先を開いて所在地を Google Geocoding API に渡し、位置情報を取得するとともにユーザー関数 storeOBpointToDB で DB登録する。

このスクレイピングについては、国交省の発表書式が変わる可能性もあり、その場合は各自でパターンを改良するなり対応をお願いしたい。
解説:観測地点情報の取り出し
0343: /**
0344: * 国交省「川の防災情報」をスクレイピングし、住所を返す
0345: * @param string $url スクレイピングするURL
0346: * @return string 住所/FALSE:失敗
0347: */
0348: function getAddress($url) {
0349: static $pat1 = "/<td\sclass\=\"tb1td2Left\">/iu";
0350:
0351: $infp =fopen($url, 'r');
0352: if ($infp == FALSE) return FALSE;
0353: $res = FALSE;
0354: while (! feof($infp)) {
0355: $str = fgets($infp);
0356: //住所
0357: if (preg_match($pat1, $str) > 0) {
0358: $res = trim(fgets($infp));
0359: break;
0360: }
0361: }
0362: fclose($infp);
0363:
0364: return $res;
0365: }
そして、この所在地を使って、位置情報(緯度・経度)を取り出すのがユーザー関数 getLatLong である。
解説:DBの初期化
0176: /**
0177: * DBの初期化
0178: * @param なし
0179: * @return bool TRUE/FALSE
0180: */
0181: function initDB() {
0182: try {
0183: $pdo = new PDO('sqlite:' . DBFILE);
0184: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
0185:
0186: //テーブル作成:出演者情報
0187: //id:観測地点ID, title:地点名,address:住所
0188: //latitude:緯度, longitude:経度
0189: //premiere:登録日時, latest:更新日時
0190: $pdo->exec('CREATE TABLE IF NOT EXISTS ' . TABLE_OBPOINT . '(
0191: id INTEGER PRIMARY KEY AUTOINCREMENT,
0192: title TEXT,
0193: address TEXT,
0194: latitude REAL,
0195: longitude REAL,
0196: premiere TEXT,
0197: latest TEXT
0198: )');
0199: $res = TRUE;
0200: } catch (PDOException $e) {
0201: $res = FALSE;
0202: }
0203:
0204: return $res;
0205: }
解説:DBから地点情報を取得する
0207: /**
0208: * DBから地点情報を取得する
0209: * @param string $id 地点ID
0210: * @param array $item 地点情報を格納する配列
0211: * @return bool TRUE:取得成功/FALSE:失敗(DB登録がない)
0212: */
0213: function getOBpointFromDB($id, &$item) {
0214: //DB検索
0215: try {
0216: $pdo = new PDO('sqlite:' . DBFILE);
0217: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
0218: $stmt = $pdo->prepare(PRE_SELECT);
0219: $stmt->bindValue(':id', $id, PDO::PARAM_STR);
0220: $res = $stmt->execute();
0221: $row = $stmt->fetch();
0222: if ($row == FALSE) {
0223: $pdo = NULL;
0224: return FALSE;
0225: }
0226: $item['title'] = $row['title'];
0227: $item['address'] = $row['address'];
0228: $item['latitude'] = $row['latitude'];
0229: $item['longitude'] = $row['longitude'];
0230: $pdo = NULL;
0231: } catch (PDOException $e) {
0232: $pdo = NULL;
0233: return FALSE;
0234: }
0235:
0236: return TRUE;
0237: }
観測地点 ID がヒットしなければ、FALSE を返す。
解説:DBに地点情報を追加する
0239: /**
0240: * DBに地点情報を追加する
0241: * @param string $id 地点ID
0242: * @param array $item 地点情報を格納する配列
0243: * @return bool TRUE:登録成功/FALSE:登録失敗
0244: */
0245: function storeOBpointToDB($id, $item) {
0246: if (!isset($item['latitude']) || !$item['longitude']) return FALSE;
0247: //DB追加
0248: try {
0249: $dt = date(DATE_W3C, time());
0250: $pdo = new PDO('sqlite:' . DBFILE);
0251: $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
0252: $stmt = $pdo->prepare(PRE_INSERT);
0253: $stmt->bindValue(':id', $id, PDO::PARAM_STR);
0254: $stmt->bindValue(':title', $item['title'], PDO::PARAM_STR);
0255: $stmt->bindValue(':address', $item['address'], PDO::PARAM_STR);
0256: $stmt->bindValue(':latitude', $item['latitude'], PDO::PARAM_STR);
0257: $stmt->bindValue(':longitude', $item['longitude'], PDO::PARAM_STR);
0258: $stmt->bindValue(':premiere', $dt, PDO::PARAM_STR);
0259: $stmt->bindValue(':latest', $dt, PDO::PARAM_STR);
0260: $row = $stmt->execute();
0261: if ($row == FALSE) {
0262: $pdo = NULL;
0263: return FALSE;
0264: }
0265: $pdo = NULL;
0266: } catch (PDOException $e) {
0267: $pdo = NULL;
0268: return FALSE;
0269: }
0270:
0271: return TRUE;
0272: }
解説:メイン・プログラム
0489: //オブジェクト生成
0490: $pgc = new pahooGeoCode();
0491:
0492: $items = array();
0493: $message = $errmsg = '';
0494: initDB();
0495:
0496: $n = getRiverInfo(RIVER_URL, $items);
0497: if ($n > 0) {
0498: setDescription($items);
0499: } else {
0500: $errmsg = '注意情報はない';
0501: }
0502:
0503: //緯度・経度情報のない観測地点を排除;version 1.01
0504: foreach ($items as $key=>$item) {
0505: if (!isset($item['latitude']) || !$item['longitude']) {
0506: unset($items[$key]);
0507: }
0508: }
0509:
0510: //マップ描画スクリプト
0511: $jsmap = $pgc->drawJSMap(MAPID, DEF_LATITUDE, DEF_LONGITUDE, DEF_TYPE, DEF_ZOOM, NULL, $items, MAPSERVICE);
0512:
0513: $HtmlBody = makeCommonBody($items, $jsmap, $errmsg);
0514:
0515: // 表示処理
0516: echo $HtmlHeader;
0517: echo $HtmlBody;
0518: echo $HtmlFooter;
次に、getRiverInfo を実行し、氾濫注意情報があればマップ上に表示する情報を setDescription を使って生成する。
最後に、メソッド pahooGeoCode::drawGMap を使って Google マップを作成する。
参考サイト
- 川の防災情報:国土交通省
- 基準値を超えた水位観測所:国土交通省
- PHP で緯度・経度から住所を求める:ぱふぅ家のホームページ
そこで今回は、国土交通省「川の防災情報」をリアルタイム参照し、河川の水位が氾濫注意水位以上の地点を Google マップにマッピングするプログラムを作る。応答速度をあげるために、Google Geocoding API を使って取得した地点情報はデータベース(SQLite)に格納してゆく。
高潮被害については、「PHP で潮位を計算する」をあわせて参考にしていただきたい。
2021 年(令和 3 年)3 月 23 日、川の防災情報(国土交通省)がリニューアルしたため、ここで紹介しているプログラムは動作しなくなった。ツール等による情報取得を控えてほしい旨の掲示があることから、本ページは 2021 年(令和 3 年)4 月末を目処に削除する予定である。悪しからずご了承願いたい。
(2020 年 11 月 3 日)RIVER_URL 変更,地理院地図・ OSM に対応