優秀な逆ジオコーディングサービスはすでに世にたくさんありますが、
- 制約が多い
- APIキーの取得とか面倒
- 不要な情報が多く、実装が面倒
みたいな理由で自分で作ってみたくなることもあるかと思います。
今回はそんな場合の一つのサンプルをご提示。
データ収集
逆ジオコーディングするためには、位置情報と住所名がマッピングされたデータが必要です。
無料でやりたいので、以下からダウンロードしました。
ダウンロードサービスに進み、都道府県単位を選択。
全ての都道府県を選択にチェックをつけ、ファイル選択画面へ遷移。
ファイル選択画面では 全ての大字・町丁目レベルを選択 にチェックをつけ、選択。
利用約款の表示画面で約款を読み、同意します。
そうするとダウンロード画面に遷移できますので、全部のボタンを押してファイルをダウンロードしてください。
URL的には、版数と都道府県コードで規則性のあるファイルパスになっているようですが、
けっして横着しようとしないで、一つ一つダウンロードボタンを押してあげてください。
沖縄県 大字・町丁目レベル 4.0b版 → http://nlftp.mlit.go.jp/isj/dls/data/04.0b/47000-04.0b.zip
なお、より精度の高い情報を使用したい場合は街区レベルのファイルもダウンロードすると良いと思います。
データ作成
全てのデータを集め終わったら、それを全部SQLiteにいれます。
元データはCSVですので、とりあえずはそのままの形式で突っ込みます。
あとは使いやすい感じに整理してデータを入れてあげてください。
私は以下のような構造にしてみました。
CREATE TABLE gis_citycode (
citycode text NOT NULL, -- 市区コード
cityname text -- 市名
)
CREATE TABLE gis_locations (
latitude numeric,
longitude numeric,
citycode text,
streetname text
)
CREATE UNIQUE INDEX index_citycode ON gis_citycode (citycode)
CREATE INDEX "index_latitude" ON "gis_locations" ("latitude" ASC)
CREATE INDEX "index_longitude" ON "gis_locations" ("longitude" ASC)
データ更新しないので主キーも作りませんでした(てへぺろ☆
SQLiteにデータを突っ込んだものはこちらになります(※2011年10月頃作成)
http://content.yosiopp.net/files/gis.zip
データ参照
あとはデータ参照側を作るだけ。phpでさくっと作ります。
成功時にはtext/plainで値を返す仕様にしました。
エラーの場合は状況に応じてそれっぽいHTTPステータスを返します。(400, 404, 500)
ここら辺の仕様を適当に決められるのがオレオレ逆ジオコーディングの良いところですね。
<?php
// パラメタが不十分の場合エラー
if(!isset($_GET['lat']) || !isset($_GET['lon'])){
header('HTTP', true, 400); // bad request
exit(1);
}
// パラメタが不正の場合エラー
$pattern = "/\d+(\.\d+)?/";
if(preg_match($pattern, $_GET['lat']) == 0 || preg_match($pattern, $_GET['lon']) == 0){
header('HTTP', true, 400); // bad request
exit(2);
}
// パラメタ取得
$lat = $_GET['lat'] + 0.0;
$lon = $_GET['lon'] + 0.0;
$sql =<<<SQL
select
substr(t1.citycode,1,2),
(select cityname from gis_citycode where gis_citycode.citycode = t1.citycode),
streetname,
abs(d1 + d2) as d3
from
(
select abs(latitude - :lat) as d1, *
from gis_locations
where d1 < :dist
order by d1
) as t1
join (
select abs(longitude - :lon) as d2, *
from gis_locations
where d2 < :dist
order by d2
) as t2 using(citycode, streetname)
order by d3
limit 1
SQL;
try {
$dist = 0.1;
$pdo = new PDO('sqlite:gis.db');
$pdo->beginTransaction();
$stmt = $pdo->prepare($sql);
$stmt->bindParam(":lat", $lat);
$stmt->bindParam(":lon", $lon);
$stmt->bindParam(":dist", $dist);
$stmt->execute();
$stmt->bindColumn(1, $state);
$stmt->bindColumn(2, $city);
$stmt->bindColumn(3, $street);
if($stmt->fetch() === FALSE){
// 閾値を下げて再試行
$dist = 0.5;
$stmt->bindParam(":dist", $dist);
$stmt->execute();
if($stmt->fetch() === FALSE){
// 見つからない
header('HTTP', true, 404);
}
}
$pdo->commit();
$arry = array("01"=>"北海道","02"=>"青森県","03"=>"岩手県","04"=>"宮城県",
"05"=>"秋田県","06"=>"山形県","07"=>"福島県","08"=>"茨城県",
"09"=>"栃木県","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"=>"沖縄県");
// 出力
header("content-type: text/plain", true);
echo($arry[$state] ." ". $city ." ". $street);
}
catch( Throwable $t ) {
// サーバエラー
header('HTTP', true, 500); // internal server error
echo $t;
exit(3);
}
?>
だいたい見てもらえばわかると思います。
GETパラメタのlatとlonの値を入力とし、DB上の値との差の絶対値を取り、近い順にソートして先頭のもの(=一番近い住所)を返します。
あ、データ上、citycodeの前2桁が都道府県コードとなってます。
文字列とのマッピングは見ての通りphp上で行ってます。
性能はあんまり考えてません。動けばいいかなレベル。
結構昔にふと思って作ったものなので…(言い訳