ひつじかいさんの「ひつじかいの雑記帳」というブログを拝見し、その中に、地図の描写において色々と興味深い記事を拝読しました。これは自分でもやってみたい、そう思い、その記録を記載してみたいと思います。
Web上で操作可能な日本の白地図(都道府県別)を作る(4) ― レコード数削減(データ抽出) ―
前回に引き続き、データを間引く作業です。ひつじかいさんのブログには、データが約260万件との記載がありますが、今回試した平成31年データでは約960万件と3倍以上のようです。
データを間引く方法は、点と点との間隔を計算し、加えて点を緯度・経度でグループ化して処理していきます。まず次のSQL文を実行させフィールドを加えます。
ALTER TABLE t_PrefectureBorder ADD ExtractFlag TINYINT(2) NOT NULL DEFAULT 0
そして解説頂いたPHPのソースコードは次の通りです。ひつじかいさんの記載されている内容と変わりありません。
<?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++) { echo "Pref=" . $PrefCode . "<br>"; echo " ----- Reading Data -----<br>"; ob_flush(); flush(); $border = array(); $strSQL = "SELECT t_PrefectureBorder.*"; $strSQL .= " FROM t_PrefectureBorder"; $strSQL .= " INNER JOIN t_PrefectureBorderArea"; $strSQL .= " ON t_PrefectureBorder.PrefectureCode = t_PrefectureBorderArea.PrefectureCode"; $strSQL .= " AND t_PrefectureBorder.BorderCode = t_PrefectureBorderArea.BorderCode"; $strSQL .= " WHERE t_PrefectureBorderArea.PrefectureCode = " . $PrefCode; $strSQL .= " AND t_PrefectureBorderArea.ActiveFlag = 1"; $strSQL .= " ORDER BY t_PrefectureBorder.BorderCode, t_PrefectureBorder.PointNo"; $rst = $mysqli->query($strSQL); while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { array_push($border, $col); } $rst->close(); for ($b = 0; $b < count($border); $b++) { $border[$b]["PrefGrp"] = GroupPointLatLng($mysqli, $border[$b]); //ブラウザがタイムアウトしないよう1万件毎に出力 if ($b % 10000 == 0) { echo " PrefCode=" . $border[$b]["PrefectureCode"] . " BorderCode=" . $border[$b]["BorderCode"] . " PointNo=" . $border[$b]["PointNo"] . " " . $b . "<br>"; ob_flush(); flush(); } } $intervals = 1000; //点と点の間隔 echo " ----- Active Point -----<br>"; $formerborder = array(); for ($b = 0; $b < count($border); $b++) { if (count($border[$b]["PrefGrp"]) > 0 && $border[$b]["PrefGrp"][0] == $PrefCode) { if ( !isset($formerborder["BorderCode"]) || $border[$b]["BorderCode"] <> $formerborder["BorderCode"] || count($border[$b]["PrefGrp"]) > count($formerborder["PrefGrp"]) || ($b + 1 < count($border) && count($border[$b]["PrefGrp"]) > count($border[$b + 1]["PrefGrp"])) || getDistance($formerborder, $border[$b]) > $intervals ) { $formerborder = $border[$b]; $strSQL = "UPDATE t_PrefectureBorder"; $strSQL .= " SET ExtractFlag = 1"; $strSQL .= " WHERE Lat = " . $border[$b]["Lat"]; $strSQL .= " AND Lng = " . $border[$b]["Lng"]; if (!$mysqli->query($strSQL)) { echo "失敗!" . $strSQL . "<br>"; exit(); } //抽出された点の情報をブラウザ出力(タイムアウト対策) echo " PrefCode=" . $border[$b]["PrefectureCode"] . " BorderCode=" . $border[$b]["BorderCode"] . " PointNo=" . $border[$b]["PointNo"] . " Grp=" . implode("-", $border[$b]["PrefGrp"]) . "<br>"; ob_flush(); flush(); } } } } if ($mysqli) { $mysqli->close(); } echo "終了しました。<br>"; ini_restore('error_reporting'); ini_restore('memory_limit'); //対象の点($point)と同じ緯度・経度の点を探索して配列で返す function GroupPointLatLng($mysqli, $point) { $strSQL = "SELECT DISTINCT PrefectureCode FROM t_PrefectureBorder"; $strSQL .= " WHERE Lat = " . $point["Lat"] . " AND Lng = " . $point["Lng"]; $strSQL .= " ORDER BY PrefectureCode"; $rst = $mysqli->query($strSQL); $prefgrp = array(); while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { array_push($prefgrp, $col["PrefectureCode"]); } $rst->close(); return $prefgrp; } //2点間の距離を計算(GRS80) // 引数:$p0,$p1 --- 緯度・経度収納の配列 array("Lat"=>latitude,"Lng"=>longitude) function getDistance($p0, $p1) { $a = 6378137.0; //長半径 $f = 1.0 / 298.257222101; //扁平率 $b = $a * (1 - $f); //短半径 $e2 = (pow($a, 2) - pow($b, 2)) / pow($a, 2); //第一離心率^2 $dy = deg2rad($p0["Lat"] - $p1["Lat"]); //2点の緯度差(ラジアン) $dx = deg2rad($p0["Lng"] - $p1["Lng"]); //2点の経度差(ラジアン) $my = deg2rad(($p0["Lat"] + $p1["Lat"]) / 2.0); //2点の緯度の中間 $w = sqrt(1.0 - $e2 * pow(sin($my), 2)); $m = ($a * (1 - $e2)) / pow($w, 3); //子午線曲率半径 $n = $a / $w; //卯酉線曲率半径 return sqrt(pow($dy * $m, 2) + pow($dx * $n * cos($my), 2)); } //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; } }
これで設定値である1000mごとのポイント、かつ隣接する県境に対する対応ができています。SQL文でデータ数を確認すると3万3千で、ひつじかいさんの計算結果とここで一致します。これを白地図で表示してみます。
白地図は描けており問題ありませんが、データ抽出において時間を要しているようなので、新たなテーブルを作成します。
[SQL] CREATE TABLE t_JapanPrefectureBorder ( 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文を実行し、生成した[t_JapanPrefectureBorder]にデータをコピーしていきます。
<?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++) { echo "PrefCode=" . $PrefCode . "<br>"; ob_flush(); flush(); $oldBorderCode = array(); $strSQL = "SELECT DISTINCT BorderCode"; $strSQL .= " FROM t_PrefectureBorder"; $strSQL .= " WHERE t_PrefectureBorder.PrefectureCode = " . $PrefCode; $strSQL .= " AND t_PrefectureBorder.ExtractFlag = 1"; $strSQL .= " ORDER BY t_PrefectureBorder.BorderCode"; $rst = $mysqli->query($strSQL); while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { array_push($oldBorderCode, $col["BorderCode"]); } $rst->close(); for ($i = 0; $i < count($oldBorderCode); $i++) { echo "old bordercode=" . $oldBorderCode[$i] . "---> new bordercode=" . $i . "<br>"; $strSQL = "SELECT * FROM t_PrefectureBorder"; $strSQL .= " WHERE PrefectureCode = " . $PrefCode; $strSQL .= " AND BorderCode = " . $oldBorderCode[$i]; $strSQL .= " AND ExtractFlag = 1"; $strSQL .= " ORDER BY BorderCode"; $rst = $mysqli->query($strSQL); $point = array(); while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { array_push($point, $col); } $rst->close(); for ($j = 0; $j < count($point); $j++) { $strSQL = "INSERT INTO t_JapanPrefectureBorder (PrefectureCode,BorderCode,PointNo,Lat,Lng)"; $strSQL .= " VALUES (" . $PrefCode . "," . $i . "," . $j . "," . $point[$j]["Lat"] . "," . $point[$j]["Lng"] . ")"; if (!$mysqli->query($strSQL)) { echo "失敗!" . $strSQL . "<br>"; exit(); } } } } if ($mysqli) { $mysqli->close(); } echo "終了しました。<br>"; ini_restore('error_reporting'); ini_restore('memory_limit'); //対象の点($point)と同じ緯度・経度の点を探索して配列で返す function GroupPointLatLng($mysqli, $point) { $strSQL = "SELECT DISTINCT PrefectureCode FROM t_PrefectureBorder"; $strSQL .= " WHERE Lat = " . $point["Lat"] . " AND Lng = " . $point["Lng"]; $strSQL .= " ORDER BY PrefectureCode"; $rst = $mysqli->query($strSQL); $prefgrp = array(); while ($col = $rst->fetch_array(MYSQLI_ASSOC)) { array_push($prefgrp, $col["PrefectureCode"]); } $rst->close(); return $prefgrp; } //2点間の距離を計算(GRS80) // 引数:$p0,$p1 --- 緯度・経度収納の配列 array("Lat"=>latitude,"Lng"=>longitude) function getDistance($p0, $p1) { $a = 6378137.0; //長半径 $f = 1.0 / 298.257222101; //扁平率 $b = $a * (1 - $f); //短半径 $e2 = (pow($a, 2) - pow($b, 2)) / pow($a, 2); //第一離心率^2 $dy = deg2rad($p0["Lat"] - $p1["Lat"]); //2点の緯度差(ラジアン) $dx = deg2rad($p0["Lng"] - $p1["Lng"]); //2点の経度差(ラジアン) $my = deg2rad(($p0["Lat"] + $p1["Lat"]) / 2.0); //2点の緯度の中間 $w = sqrt(1.0 - $e2 * pow(sin($my), 2)); $m = ($a * (1 - $e2)) / pow($w, 3); //子午線曲率半径 $n = $a / $w; //卯酉線曲率半径 return sqrt(pow($dy * $m, 2) + pow($dx * $n * cos($my), 2)); } //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]から[t_JapanPrefectureBorder]に変更し、白地図を描いてみます。結果は前述と同じですが、途中の計算スピードは圧倒的にアップしています。
それにしても、隣接する県境においてグルーピングする方法を教示されていますが、私では考えられないこと、勉強になります。