ひつじかいさんの「ひつじかいの雑記帳」というブログを拝見し、その中に、地図の描写において色々と興味深い記事を拝読しました。これは自分でもやってみたい、そう思い、その記録を記載してみたいと思います。
Web上で操作可能な日本の白地図(都道府県別)を作る(2) ― 市区町村境データより都道府県の輪郭を取得 ―
今回の取り組みは、都道府県データを抜き出すことです。ブログにはご苦労されたとの記載があり、その結果をこんな形で楽に学習させていただくことに感謝です。
前回処理でt_pointは1400万件以上データがあるので、都道府県ごとに分割します。PHPソースコードは、ひつじかいさんのままで変更はありません。
<?php ini_set('error_reporting', ~E_WARNING); ini_set('memory_limit', '1024M'); set_time_limit(0); $errMsg; $mysqli = connectDBi($errMsg); if ($mysqli) { echo "MySQL接続OK<br>"; } else { echo "MySQL接続NG<br>" . $errMsg; exit(); } echo str_pad(" ", 4096) . "<br>"; for ($PrefCode = 1; $PrefCode <= 47; $PrefCode++) { $strSQL = "CREATE TABLE IF NOT EXISTS t_Point_Pref" . sprintf("%02d", $PrefCode) . " ("; $strSQL .= " PrefectureCode smallint(2) unsigned zerofill NOT NULL,"; $strSQL .= " AdministrativeAreaCode int(5) unsigned zerofill NOT NULL DEFAULT '00000',"; $strSQL .= " SurfaceCode int(5) NOT NULL,"; $strSQL .= " CurveCode int(5) NOT NULL,"; $strSQL .= " PointNo int(5) NOT NULL DEFAULT '0',"; $strSQL .= " Lat decimal(15,8) DEFAULT NULL,"; $strSQL .= " Lng decimal(15,8) DEFAULT NULL,"; $strSQL .= " CityBorderFlag tinyint(2) NOT NULL DEFAULT '0',"; $strSQL .= " PRIMARY KEY (PrefectureCode,AdministrativeAreaCode,SurfaceCode,CurveCode,PointNo),"; $strSQL .= " UNIQUE (SurfaceCode,CurveCode,PointNo),"; $strSQL .= " KEY LatLng (Lat,Lng)"; $strSQL .= ") ENGINE=MyISAM DEFAULT CHARSET=utf8"; if (!$mysqli->query($strSQL)) { echo "失敗!" . $strSQL . "<br>"; exit(); } echo "Create Table : t_Point_Pref" . sprintf("%02d", $PrefCode) . "<br>"; ob_flush(); flush(); $strSQL = "INSERT INTO t_Point_Pref" . sprintf("%02d", $PrefCode) . " (PrefectureCode,AdministrativeAreaCode,SurfaceCode,CurveCode,PointNo,Lat,Lng)"; $strSQL .= " SELECT PrefectureCode,AdministrativeAreaCode,SurfaceCode,CurveCode,PointNo,Lat,Lng"; $strSQL .= " FROM t_Point WHERE PrefectureCode = " . $PrefCode; if (!$mysqli->query($strSQL)) { echo "失敗!" . $strSQL . "<br>"; exit(); } echo "INSERT DATA TO : t_Point_Pref" . sprintf("%02d", $PrefCode) . "<br>"; ob_flush(); flush(); } if ($mysqli) { $mysqli->close(); } echo "終了しました。<br>"; ini_restore('error_reporting'); ini_restore('memory_limit'); //MySQLへ接続 function connectDBi(&$err) { //MySQL 接続情報 $MySQL_SERVER = "localhost"; $MySQL_USER = "[user_name]"; $MySQL_PASSWORD = "[password]"; $MySQL_DBNAME = "map"; $err = ""; $mysqli = new mysqli($MySQL_SERVER, $MySQL_USER, $MySQL_PASSWORD, $MySQL_DBNAME); if ($mysqli->connect_errno) { $err = "データベース接続に失敗しました。"; } if (strlen($err) > 0) { return false; } else { return $mysqli; } }
そして次はこのCityBorderFlagの更新処理。同一都道府県内の市区町村境界であるか否かのチェックです。
<?php ini_set('error_reporting', ~E_WARNING); ini_set('memory_limit', '2048M'); set_time_limit(0); $errMsg; $mysqli = connectDBi($errMsg); if ($mysqli) { echo "MySQL接続OK<br>"; } else { echo "MySQL接続NG<br>" . $errMsg; exit(); } echo str_pad(" ", 4096) . "<br>"; for ($PrefCode = 1; $PrefCode <= 47; $PrefCode++) { $strSQL = "UPDATE t_Point_Pref".sprintf("%02d",$PrefCode); $strSQL .= " INNER JOIN t_Point_Pref".sprintf("%02d",$PrefCode)." AS t_Point_LatLng"; $strSQL .= " ON t_Point_Pref".sprintf("%02d",$PrefCode).".Lat = t_Point_LatLng.Lat"; $strSQL .= " AND t_Point_Pref".sprintf("%02d",$PrefCode).".Lng = t_Point_LatLng.Lng"; $strSQL .= " AND (t_Point_Pref".sprintf("%02d",$PrefCode).".SurfaceCode <> t_Point_LatLng.SurfaceCode"; $strSQL .= " OR t_Point_Pref".sprintf("%02d",$PrefCode).".CurveCode <> t_Point_LatLng.CurveCode)"; $strSQL .= " SET t_Point_Pref".sprintf("%02d",$PrefCode).".CityBorderFlag = 1"; if (!$mysqli->query($strSQL)){ echo "失敗!". $strSQL ."<br>"; exit(); } echo "UPDATE CityBorderFlag : t_Point_Pref".sprintf("%02d",$PrefCode)."<br>"; ob_flush(); flush(); } //MySQLへ接続 function connectDBi(&$err) { //MySQL 接続情報 $MySQL_SERVER = "localhost"; $MySQL_USER = "[user_name]"; $MySQL_PASSWORD = "[password]"; $MySQL_DBNAME = "map"; $err = ""; $mysqli = new mysqli($MySQL_SERVER, $MySQL_USER, $MySQL_PASSWORD, $MySQL_DBNAME); if ($mysqli->connect_errno) { $err = "データベース接続に失敗しました。"; } if (strlen($err) > 0) { return false; } else { return $mysqli; } }
次に都道府県境用のテーブルを用意します。
・t_PrefectureBorder [SQL] CREATE TABLE IF NOT EXISTS `t_PrefectureBorder` ( `PrefectureCode` smallint(2) unsigned zerofill NOT NULL, `BorderCode` int(5) NOT NULL, `PointNo` int(5) NOT NULL DEFAULT 0, `Lat` decimal(15,8) DEFAULT NULL, `Lng` decimal(15,8) DEFAULT NULL, PRIMARY KEY (`PrefectureCode`,`BorderCode`,`PointNo`), KEY `LatLng` (`Lat`,`Lng`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; [/SQL]
そして都道府県データの生成です。こちらも、ひつじかいさんのソースそのままです。
<?php ini_set('error_reporting', ~E_WARNING); ini_set('memory_limit', '2048M'); set_time_limit(0); $errMsg; $mysqli = connectDBi($errMsg); if ($mysqli) { echo "MySQL接続OK<br>"; } else { echo "MySQL接続NG<br>" . $errMsg; exit(); } echo str_pad(" ", 4096) . "<br>"; for ($PrefCode = 1; $PrefCode <= 47; $PrefCode++) { $strSQL = "SELECT t_Point_Pref" . sprintf("%02d", $PrefCode) . ".* FROM t_Point_Pref" . sprintf("%02d", $PrefCode); $strSQL .= " INNER JOIN"; $strSQL .= " (SELECT PrefectureCode, AdministrativeAreaCode, SurfaceCode, CurveCode, Min(CityBorderFlag), Max(CityBorderFlag)"; $strSQL .= " FROM t_Point_Pref" . sprintf("%02d", $PrefCode); $strSQL .= " GROUP BY PrefectureCode, AdministrativeAreaCode, SurfaceCode, CurveCode"; $strSQL .= " HAVING Min(CityBorderFlag)=0 AND Max(CityBorderFlag)=1) AS q_BorderCurve"; $strSQL .= " ON t_Point_Pref" . sprintf("%02d", $PrefCode) . ".PrefectureCode = q_BorderCurve.PrefectureCode"; $strSQL .= " AND t_Point_Pref" . sprintf("%02d", $PrefCode) . ".AdministrativeAreaCode = q_BorderCurve.AdministrativeAreaCode"; $strSQL .= " AND t_Point_Pref" . sprintf("%02d", $PrefCode) . ".SurfaceCode = q_BorderCurve.SurfaceCode"; $strSQL .= " AND t_Point_Pref" . sprintf("%02d", $PrefCode) . ".CurveCode = q_BorderCurve.CurveCode"; $strSQL .= " ORDER BY SurfaceCode, CurveCode, PointNo"; echo "Pref=" . $PrefCode . "<br>"; echo " ----- Reading Point Data -----<br>"; ob_flush(); flush(); $rst = $mysqli->query($strSQL); $rcount = $rst->num_rows; $pointInfo = array(); $connectpoint = array(); $i = 0; $j = 0; $formerpoint = array(); if ($rcount > 0) { while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { if ( isset($formerpoint["PrefectureCode"]) && $formerpoint["PrefectureCode"] == $PrefCode && ($formerpoint["SurfaceCode"] <> $col["SurfaceCode"] || $formerpoint["CurveCode"] <> $col["CurveCode"]) ) { $i++; $j = 0; } $col["iNo"] = $i; $col["jNo"] = $j; $col["CheckedFlagP"] = 0; $col["CheckedFlagS"] = 0; $pointInfo[$i][$j] = $col; if ($j > 0) { if (!$pointInfo[$i][$j - 1]["CityBorderFlag"] && $pointInfo[$i][$j]["CityBorderFlag"]) { array_push($connectpoint, $pointInfo[$i][$j]); } else if ($pointInfo[$i][$j - 1]["CityBorderFlag"] && !$pointInfo[$i][$j]["CityBorderFlag"]) { array_push($connectpoint, $pointInfo[$i][$j - 1]); } } $formerpoint = $col; $j++; } } $border = array(); $formerp = Null; $p = get_BorderStartPoint($pointInfo); $bNo = 0; while (is_array($p)) { if (!isset($border[$bNo])) { $border[$bNo] = array(); } if (!isset($formerp) || $p["Lat"] <> $formerp["Lat"] || $p["Lng"] <> $formerp["Lng"]) { array_push($border[$bNo], $p); //前の点と同じ緯度経度の場合は登録しない } $pointInfo[$p["iNo"]][0]["CheckedFlagS"] = 1; //チェック(領域単位) $pointInfo[$p["iNo"]][$p["jNo"]]["CheckedFlagP"] = 1; //チェック(ポイント単位) $formerp = $p; $p = get_NextPoint($pointInfo, $connectpoint, $p); if (is_null($p)) { $p = get_BorderStartPoint($pointInfo); $bNo++; } } for ($i = 0; $i < count($border); $i++) { //開始点と終了点の座標が同じになるよう調整 if (count($border[$i]) > 1) { if ( $border[$i][0]["Lat"] <> $border[$i][count($border[$i]) - 1]["Lat"] || $border[$i][0]["Lng"] <> $border[$i][count($border[$i]) - 1]["Lng"] ) { array_push($border[$i], $border[$i][0]); } } echo "INSERT to t_PrefectureBorder: BorderCode=" . $i . " record count:" . count($border[$i]) . "<br>"; ob_flush(); flush(); for ($j = 0; $j < count($border[$i]); $j++) { $strSQL = "INSERT INTO t_PrefectureBorder (PrefectureCode,BorderCode,PointNo,Lat,Lng)"; $strSQL .= " VALUES (" . $PrefCode . "," . $i . "," . $j . ","; $strSQL .= $border[$i][$j]["Lat"] . "," . $border[$i][$j]["Lng"] . ")"; if (!$mysqli->query($strSQL)) { echo "失敗!" . $strSQL . "<br>"; exit(); } } } //境界線が全て都道府県境となる領域(島など) $strSQL = "SELECT PrefectureCode, AdministrativeAreaCode, SurfaceCode, CurveCode, Min(CityBorderFlag), Max(CityBorderFlag)"; $strSQL .= " FROM t_Point_Pref" . sprintf("%02d", $PrefCode); $strSQL .= " GROUP BY PrefectureCode, AdministrativeAreaCode, SurfaceCode, CurveCode"; $strSQL .= " HAVING Max(CityBorderFlag)=0"; $rst = $mysqli->query($strSQL); $rcount = $rst->num_rows; if ($rcount > 0) { $bNo = count($border); while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { $strSQL = "INSERT INTO t_PrefectureBorder (PrefectureCode,BorderCode,PointNo,Lat,Lng)"; $strSQL .= " SELECT PrefectureCode," . $bNo . ",PointNo,Lat,Lng"; $strSQL .= " FROM t_Point_Pref" . sprintf("%02d", $PrefCode); $strSQL .= " WHERE PrefectureCode = " . $col["PrefectureCode"]; $strSQL .= " AND AdministrativeAreaCode = " . $col["AdministrativeAreaCode"]; $strSQL .= " AND SurfaceCode = " . $col["SurfaceCode"]; $strSQL .= " AND CurveCode = " . $col["CurveCode"]; echo "INSERT to t_PrefectureBorder: BorderCode=" . $bNo . " (islands and others)<br>"; ob_flush(); flush(); if (!$mysqli->query($strSQL)) { echo "失敗!" . $strSQL . "<br>"; exit(); } $bNo++; } } } if ($mysqli) { $mysqli->close(); } echo "終了しました。<br>"; ini_restore('error_reporting'); ini_restore('memory_limit'); //輪郭の始点となる点を設定 function get_BorderStartPoint($pointInfo) { echo " ----- Get Border Start Point -----<br>"; ob_flush(); flush(); for ($i = 0; $i < count($pointInfo); $i++) { if (!$pointInfo[$i][0]["CheckedFlagS"]) { //チェック済みの点のある領域がスタート地点になることはない for ($j = 0; $j < count($pointInfo[$i]); $j++) { if (!$pointInfo[$i][$j]["CityBorderFlag"]) { //出力 foreach ($pointInfo[$i][$j] as $key => $value) { echo $value . " "; } echo "<br>"; ob_flush(); flush(); return $pointInfo[$i][$j]; } } } } echo " not found<br>"; ob_flush(); flush(); return NULL; //見つからない(=全ての点をチェックした)場合 } //都道府県輪郭線の次の点を探す function get_NextPoint($pointInfo, $connectpoint, $p) { $i = $p["iNo"]; $j = $p["jNo"] + 1; if ($j >= count($pointInfo[$i])) { $j = 0; } if (!$pointInfo[$i][$j]["CheckedFlagP"]) { if (!$pointInfo[$i][$j]["CityBorderFlag"]) { return $pointInfo[$i][$j]; } else { return get_ConnectCityBorder($connectpoint, $pointInfo[$i][$j]); } } else { return NULL; //チェック済みの点に戻ってきた(=1周した) } } //市区町村の変わり目(接続点)を取得 function get_ConnectCityBorder($connectpoint, $p) { echo "----- Connect City Border -----<br>"; $cp = array(); for ($c = 0; $c < count($connectpoint); $c++) { if ($connectpoint[$c]["Lat"] == $p["Lat"] && $connectpoint[$c]["Lng"] == $p["Lng"]) { array_push($cp, $connectpoint[$c]); } } //3つ以上の市町村境である点を考慮し、接続元の市区町村の次に登録されている点を返す if (count($cp) > 0) { $chk = 0; $cc = 0; for ($c = 0; $c < count($cp); $c++) { if ($cp[$c]["iNo"] == $p["iNo"]) { $chk = 1; } else { if ($chk == 1) { $cc = $c; break; } } } //出力 foreach ($cp[$cc] as $key => $value) { echo $value . " "; } echo "<br>"; ob_flush(); flush(); return $cp[$cc]; } return NULL; //見つからなかった場合(通常はあり得ないはず) } //MySQLへ接続 function connectDBi(&$err) { //MySQL 接続情報 $MySQL_SERVER = "localhost"; $MySQL_USER = "*****"; $MySQL_PASSWORD = "*****"; $MySQL_DBNAME = "map"; $err = ""; $mysqli = new mysqli($MySQL_SERVER, $MySQL_USER, $MySQL_PASSWORD, $MySQL_DBNAME); if ($mysqli->connect_errno) { $err = "データベース接続に失敗しました。"; } if (strlen($err) > 0) { return false; } else { return $mysqli; } }
ソースコードの解説については、ひつじかいさんのブログで詳しく記載されています。また、ひつじかいさんのWEBソースコード(HTML/JavaScript)を拝借し、PHP部分は自分で記載してみました。ちゃんと描けているようです。
データ数の多い、長崎県・鹿児島県のデータも表示させてみました。確かに多少待たされますが、特に大きな問題はないようです(長崎県は見た目、本土と諸島との面積は変わらないくらいなのですね)。
ひつじかいさんの行間に含まれるご苦労を感じながら、ありがたく学ばせていただいています。