郵便番号を入力したら半径3km内のすべての町名を取得したいという要望がありました。

この要望を聞いた時に頭に浮かんだのがGoogleのGeocoding APIだったので、このAPIで指定の場所の周辺の町名を取得してみる。

Overview  |  Geocoding API  |  Google Developers


Geocoding APIはGoogle Maps Platformで支払い方法の設定をしていないと使用できない。

これから記載する内容はGoogle Maps Platformでの設定を終了していることを前提として話を進める。

位置情報 API | Google Maps Platform | Google Cloud

※200ドル分/月までの使用は無料(1000リクエストで10ドルらしい)


Geocoding APIはGETを渡すことで利用することができるAPIで

https://maps.googleapis.com/maps/api/geocode/json?address=調べたい住所&key=Google Cloudで取得したキー

このようにGETで送信すると、郵便番号、住所や緯度経度の情報が返ってくる。


上記の内容を踏まえた上で周辺の町名の取得を行ってみる。

今回試すのは、



大阪府高槻市にある安岡寺(あんこうじと読む)というお寺を中心にして、半径3km以内の町名を検索してみる。

安岡寺 - Wikipedia




事前に調査してうまく行かなかったものを挙げておくと、APIのエンドポイント利用時にboundsパラメータを付与することで範囲検索ができるようになるらしいので、boundsで範囲を決めて、addressに郵便番号と入れて検索してみたけれども、結果は一つしか返ってこなかった。



次は上記のようにジオコーディングで中心(安岡寺)を調べて、中心の緯度経度を取得する。

この中心の緯度経度を元に上記のキャプチャのように少しずらした箇所の緯度経度を元に逆ジオコーディング(緯度経度から検索)を行い、新しい町名が出てきたら記録するという方針でやってみることにした。




app
├── func.php
└── index.php

即席なのでファイルの構成は雑だけれども、func.phpによく使う関数をまとめる事にした。


func.php

<?php
mb_language("Japanese");//文字コードの設定
mb_internal_encoding("UTF-8");

//Google Maps APIやGeocoding APIを使う為のAPIキー
define("GOOGLE_MAPS_KEY", "AIzaSyBwnK1hFICvT0qJQscF-xhoZQEDVumGAr4");

//建物名等から住所や緯度経度を取得
function executeApiByAddress($address){
	$url = "https://maps.googleapis.com/maps/api/geocode/json?address=" . urlencode($address) . "&language=ja&key=" . GOOGLE_MAPS_KEY;
	return json_decode(file_get_contents($url), true);
}

function executeApiByLatLng($lat, $lng){
	$url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" . $lat . "," . $lng . "&language=ja&key=" . GOOGLE_MAPS_KEY;
	return json_decode(file_get_contents($url), true);
}

//Geocoding APIの結果から郵便番号を取り出す
function getZipcode($components){
	foreach($components as $component){
		if(!isset($component["types"][0])) continue;
		if($component["types"][0] == "postal_code"){
			return $component["short_name"];
		}
	}
	return null;
}

//Geocoding APIの結果から町名を取り出す
function getTownName($components){
	foreach($components as $component){
		if(!isset($component["types"][2])) continue;

		//sublocality_level_2が町名に該当するらしい
		if($component["types"][2] == "sublocality_level_2"){
			return $component["short_name"];
		}
	}
}

//緯度経度を取得
function getLatLng($location){
	return array($location["lat"], $location["lng"]);
}

これを踏まえた上で、index.phpで網羅的に任意の箇所の周辺の町名を取得するコードを書いてみる。


index.php

<?php
include("./func.php");

//住所を入れて緯度経度を求める。
$target = "安岡寺";
$data = executeApiByAddress($target);
if($data["status"] == "OK"){
	$results = $data["results"];

	//Google Mapsで検証用
	$latlngs = array();
	list($lat, $lng) = getLatLng($results[0]["geometry"]["location"]);
	$latlngs[] = $lat . "," . $lng;

	//郵便番号と町名の取得
	$zip = getZipcode($results[0]["address_components"]);

	$zips = array();
	$zips[] = $zip;

	$towns = array();
	$towns[$zip] = getTownName($results[0]["address_components"]);

	for($i = -3; $i <= 3; $i++){
		for($j = -3; $j <= 3; $j++){
			if($i === 0 && $j === 0) continue;
			usleep(500); //スリープしておかないとnullが返ってくることがある
			$data = executeApiByLatLng(($lat - (0.01 * $i)), ($lng - (0.01 * $j)));
			if(is_null($data)) continue;

			if($data["status"] == "OK"){
				$results = $data["results"];
				list($newlat, $newlng) = getLatLng($results[0]["geometry"]["location"]);

				$zip = getZipcode($results[0]["address_components"]);
				if(strlen($zip) && is_bool(array_search($zip, $zips))){
					$zips[] = $zip;
					$towns[$zip] = getTownName($results[0]["address_components"]);
				}

				$latlngs[] = $newlat . "," . $newlng;
			}
		}
	}
}
?>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>test</title>
</head>
<body>
<h2>町名一覧</h2>
<ul>
<?php
sort($zips);
foreach($zips as $zip){
	echo "<li>" . $zip . " - " .  $towns[$zip] . "</li>";
}
?>
</ul>
</body>

上記のコードを実行してみると、

・567-0012 - 東太田
・567-0018 - 太田
・569-0024 - 日向町
・569-0056 - 城南町
・569-0064 - 庄所町
・569-0071 - 城北町
・569-0088 - 天王町
・569-0822 - 津之江町
・569-1011 - 大字川久保
・569-1012 - 大字成合
・569-1017 - 成合北の町
・569-1022 - 日吉台三番町
・569-1028 - 浦堂本町
・569-1029 - 安岡寺町
・569-1036 - 塚脇
・569-1039 - 清水台
・569-1041 - 大字奈佐原
・569-1042 - 南平台
・569-1046 - 塚原
・569-1047 - 大和
・569-1051 - 大字原
・569-1053 - 萩谷月見台
・569-1054 - 大字萩谷
・569-1101 - 安満御所の町
・569-1104 - 安満東の町
・569-1115 - 古曽部町
・569-1122 - 月見町
・569-1124 - 南芥川町
・569-1128 - 名神町
・569-1133 - 川西町
・569-1141 - 氷室町
・569-1143 - 幸町
・569-1147 - 土室町

上記のように33箇所程、郵便番号と町名を取得することができた。

今回のコードでは、中心から緯度と軽度を0.01ずつずらしてマーカーを付けている。0.01というのが大体1kmになる。

今回のコードは7✕7=49箇所を調べていて、結果を見る限りどうやらすべての町名は取得できていないらしい。


実行回数を13✕13=169箇所試してみたら、取得できた町名は増えたので、どうやら1km✕1kmのグリットではすべての住所は取得できないらしい。


今回の方法ではAPIの使用回数が心配なので、なんかもっと良い方法があれば良いのだけれども…