MBL開発部 Nです。
GPSを利用したサービスを作成する場合、緯度経度を住所に変換したくなります。
しかし商用利用の場合、有料サービスばかりですし、コストの計算やAPI取得失敗した際の動作を考えたりするの面倒ですよね。
この記事の方法でPostGISが利用出来るpostgresqlサーバがあれば日本国内の逆ジオコーディングデータベースを自前で構築することが出来ます。元データの取得も無料ですし簡単です。
こちらのe-stat 政府統計ポータルサイトの統計地理情報システムより境界データのダウンロードが出来ます。
画面リンクより
地図 > 境界データダウンロード > 小地域 >国勢調査 > 2020年 >小地域(町丁・字等別)(JGD2000)>世界測地系緯度経度・GML >北海道
とリンクを辿っていくと北海道の境界データが取得できます。
今回は 01101 札幌市中央区 という境界データをダウンロードしてみます。
ダウンロードしたzipファイルの中には r2ka01101.gml というXMLが入っています。
こちらのXMLデータから逆ジオコーディングすることだけを考えると
1 2 3 4 5 |
北海道 札幌市中央区 宮ケ丘(番地) 43.0562244012 141.3134537524 43.0560720974 141.3134730471 ~省略~43.0562244012 141.3134537524 |
この4つのタグが必要になります。
1 2 3 4 |
011010200 01 101 |
といった他のタグは今回は使いません。
では取り込んで逆ジオコーディングしてみましょう。
まずテーブルを作成します。例なので最小限のカラムで作成します。
1 2 3 4 5 6 7 |
CREATE TABLE reverse_geocoding_2020 ( pref_name text, gst_name text, city_name text, geography_data geography ); |
境界データのxmlを逆ジオコーディングテーブルにインポートするプログラムを作成します。プログラム言語はPHPです。import.phpとしましょう。
これも例なのでとりあえず動く程度のものを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<!--?php $dbconn = pg_connect("接続情報"); $sql = "INSERT INTO reverse_geocoding_2020 (pref_name,gst_name,city_name,geography_data) "; $sql .="VALUES ($1,$2,$3,st_geographyfromtext($4))"; // xmlファイルの置き場所 $extractDir = dirname(__FILE__).'/geodata/'; $aExtractFiles = scandir($extractDir); // 全国の逆ジオデータを作成する場合大量のファイルを読み込むのでループ処理出来るように foreach($aExtractFiles as $file){ pg_query($dbconn,'begin'); $aGeoInfo = array(); // scandirでは.と..も拾ってしまうので飛ばします if($file == "." || $file == ".."){ continue; } //gmlをパース $gmlPath = $extractDir.$file; $xml = simplexml_load_file($gmlPath); //ポリゴン情報 $posList = $xml--->xpath('//gml:posList'); foreach($posList as $idx => $val){ $aGeoInfo[$idx]['posList'] = json_decode(json_encode($val),true)[0]; } // 都道府県名 $KEN_NAME = $xml->xpath('//fme:PREF_NAME'); foreach($KEN_NAME as $idx => $val){ $aGeoInfo[$idx]['PREF_NAME'] = json_decode(json_encode($val),true)[0]; } // GST名(札幌市、とか) $GST_NAME = $xml->xpath('//fme:CITY_NAME'); foreach($GST_NAME as $idx => $val){ $aGeoInfo[$idx]['GST_NAME'] = json_decode(json_encode($val),true)[0]; } // CITY名(中央区、とか) $CITY_NAME = $xml->xpath('//fme:S_NAME'); foreach($CITY_NAME as $idx => $val){ if (isset(json_decode(json_encode($val),true)[0])){ $aGeoInfo[$idx]['CITY_NAME'] = json_decode(json_encode($val),true)[0]; } else { $aGeoInfo[$idx]['CITY_NAME'] = ""; } } //DBへinsert foreach($aGeoInfo as $idx => $hGeoInfo){ $pref_name = $hGeoInfo['PREF_NAME']; $gst_name = $hGeoInfo['GST_NAME']; $city_name = $hGeoInfo['CITY_NAME']; $posList = $hGeoInfo['posList']; $aPosList = explode(" ",$posList); $polytext = ''; // 経度 緯度,経度 緯度, ... としたいがXMLの中身が逆なので。 $aPosList = array_reverse($aPosList); foreach($aPosList as $i => $pos){ $polytext .= $pos; $polytext .= $i % 2 == 1 ? ',' : ' '; } $polytext = "POLYGON((".rtrim($polytext, ',')."))"; $polytext = "SRID=4326;".$polytext; $aParam = array( $pref_name, $gst_name, $city_name, $polytext ); //インサート。 if(!pg_query_params($dbconn, $sql, $aParam)){ echo "query failed. [message]".pg_last_error($dbconn)." [params]".json_encode($aParam)."\n"; pg_query($dbconn,'rollback'); } pg_query($dbconn,'commit'); } } |
実行するとreverse_geocoding_2020テーブルにデータが作成されます。
※ geography型の「geography_data」は長いため、表示の都合上省いています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
postgresql=> SELECT pref_name, gst_name, city_name FROM reverse_geocoding_2020 LIMIT 10; pref_name | gst_name | city_name -----------+--------------+------------------ 北海道 | 札幌市中央区 | 宮ケ丘(番地) 北海道 | 札幌市中央区 | 円山 北海道 | 札幌市中央区 | 円山西町(番地) 北海道 | 札幌市中央区 | 盤渓 北海道 | 札幌市中央区 | 宮の森一条一丁目 北海道 | 札幌市中央区 | 宮の森一条二丁目 北海道 | 札幌市中央区 | 円山西町(番地) 北海道 | 札幌市中央区 | 宮の森 北海道 | 札幌市中央区 | 宮の森 北海道 | 札幌市中央区 | 宮の森 (10 rows) |
では弊社の座標から住所を取得してみます。弊社は東経141.357154度 北緯43.063176度付近です。
この座標から500m以内のデータを取得し、近い順から10件取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
postgresql=> SELECT pref_name, gst_name, city_name, ST_Distance('SRID=4326;POINT(141.357154 43.063176)', geography_data) AS distance FROM reverse_geocoding_2020 WHERE ST_DWithin(geography_data, ST_GeographyFromText('SRID=4326;POINT(141.357154 43.063176)'), 500.0) ORDER BY distance LIMIT 10; pref_name | gst_name | city_name | distance -----------+--------------+----------------+-------------- 北海道 | 札幌市中央区 | 北一条東一丁目 | 0 北海道 | 札幌市中央区 | 大通東一丁目 | 41.88296018 北海道 | 札幌市中央区 | 北一条西一丁目 | 48.94226936 北海道 | 札幌市中央区 | 大通西一丁目 | 64.29599826 北海道 | 札幌市中央区 | 北二条東一丁目 | 87.9183221 北海道 | 札幌市中央区 | 北一条東二丁目 | 98.28905711 北海道 | 札幌市中央区 | 北二条西一丁目 | 101.15793347 北海道 | 札幌市中央区 | 大通東二丁目 | 106.93533364 北海道 | 札幌市中央区 | 北二条東二丁目 | 131.912594 北海道 | 札幌市中央区 | 北一条西二丁目 | 199.54985508 (10 rows) |
弊社の住所 北海道札幌市中央区北一条東一丁目 が一番近いデータとして取得できました。
国勢調査によるデータなので5年毎に更新とデータの更新スパンが長い点は有料サービス等と比べると見劣りしますが、
住所自体は頻繁に変わるものでは有りませんので大体の方にはこの方法で十分な逆ジオコーディングが出来るのではないでしょうか。