目次
- サンプル・プログラムの実行例
- サンプル・プログラム
- 準備:pahooGeoCode クラス
- 準備:住所検索サービスの選択
- サンプル・プログラムの流れ
- 準備:pahooGeoCode クラス
- 準備:逆ジオコーディングサービスの選択
- 「Google Geocoding API」による住所変換
- 解説:GoogleMaps API Geocoding
- 「Yahoo!リバースジオコーダAPI」による住所変換
- 解説:Yahoo!リバースジオコーダAPI
- 「HeartRails Geo API」による住所変換
- 解説:HeartRails Geo API
- 「簡易逆ジオコーディングサービス」による住所変換
- 解説:簡易逆ジオコーディングサービス
- 解説:検索と結果取得
- 解説:緯度・経度を正規化
- 参考サイト
サンプル・プログラムの実行例
サンプル・プログラム
search_hotel.php | サンプル・プログラム本体。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。 |
サンプル・プログラムの流れ
準備:pahooGeoCode クラス
0037: class pahooGeoCode {
0038: var $items; //検索結果格納用
0039: var $error; //エラーフラグ
0040: var $hits; //検索ヒット件数
0041: var $webapi; //直前に呼び出したWebAPI URL
0042:
0043: //Google Cloud Platform APIキー
0044: //https://cloud.google.com/maps-platform/
0045: //※Google Maps APIを利用しないのなら登録不要
0046: var $GOOGLE_API_KEY_1 = '**************************'; //HTTPリファラ用
0047: var $GOOGLE_API_KEY_2 = '**************************'; //IP制限用
0048:
0049: //Yahoo! JAPAN Webサービス アプリケーションID
0050: //https://e.developer.yahoo.co.jp/register
0051: //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
0052: var $YAHOO_APPLICATION_ID = '*****************************';
クラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。
地図や住所検索として Google を利用するのであれば、Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Webサービス アプリケーションIDが必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を、それぞれ参照されたい。
準備:逆ジオコーディングサービスの選択
0042: //逆ジオコーディングサービスの選択
0043: // 0:Google
0044: // 1:Yahoo!JAPAN
0045: // 11:HeartRails Geo API
0046: // 21:簡易ジオコーディングサービス
0047: define('REVGEOSERVICE', 11);
値 | サービス名 | 制 約 |
---|---|---|
0 | 有料(決められた無料枠あり)。 | |
1 | Yahoo!JAPAN | 無料(?)。郵便番号が取得できない。 |
11 | HeartRails Geo API | 無料。市街地ではビル名が戻ることがある。 |
21 | 簡易ジオコーディングサービス | 無料。郵便番号が取得できない。 |
「Google Geocoding API」による住所変換
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
URL |
---|
https://maps.googleapis.com/maps/api/geocode/xml |
フィールド名 | 要否 | 内 容 |
---|---|---|
key | 必須 | APIキー |
latlng | 必須 | 検索対象の緯度,経度(世界測地系,10進数の度表記) |
language | 任意 | 使用言語。ja など |
sensor | 任意 | true または false |
解説:GoogleMaps API Geocoding
得られる要素が複数あるが、type=street_address であるものが住所表記であるので、これを選別して配列に格納してゆく。
0348: /**
0349: * Google Geocoding APIを用いて緯度・経度から住所を求める
0350: * @param float $latitude 緯度(世界測地系,10進数の度表記)
0351: * @param float $longitude 経度(世界測地系,10進数の度表記)
0352: * @return array ['address'] フォーマット済み住所
0353: * ['$$$$'] 都道府県など(サービスによって添字が変わる)
0354: * FALSE=エラー
0355: */
0356: function getGoogleAddress($latitude, $longitude) {
0357: $key = $this->GOOGLE_API_KEY_2;
0358: $url = "https://maps.googleapis.com/maps/api/geocode/xml?key={$key}&latlng={$latitude},{$longitude}&language=ja®ion=JP";
0359: $this->webapi = $url;
0360:
0361: $res = array();
0362: $this->unknown_certificate();
0363: $xml = simplexml_load_file($url);
0364: //レスポンス・チェック
0365: if (preg_match("/ok/i", $xml->status) == 0) return FALSE;
0366: foreach ($xml->result as $element) {
0367: if ($element->type == 'street_address') {
0368: $res['address'] = $this->trimAddress((string)$element->formatted_address);
0369: //有効な住所部品を格納
0370: foreach ($element->address_component as $elem2) {
0371: $str = '';
0372: $flag = FALSE;
0373: foreach ($elem2->type as $val) {
0374: $type = (string)$val;
0375: if (preg_match('/_level_[0-9]+/i', $type) > 0) {
0376: $str = $type;
0377: } else if ($type == 'postal_code') {
0378: $flag = TRUE;
0379: $str = $type;
0380: } else if ($type == 'political') {
0381: $flag = TRUE;
0382: } else if ($str == '') {
0383: $str = $type;
0384: }
0385: }
0386: if ($flag && ($str != '')) {
0387: $res[$str] = (string)$elem2->long_name;
0388: }
0389: }
0390: break;
0391: }
0392: }
0393:
0394: return $res;
0395: }
「Yahoo!リバースジオコーダAPI」による住所変換
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
URL |
---|
https://map.yahooapis.jp/geoapi/V1/reverseGeoCoder |
フィールド名 | 要否 | 内 容 |
---|---|---|
appid | 必須 | アプリケーションID |
lat | 必須 | 検索対象の緯度(世界測地系,10進数の度表記) |
lng | 必須 | 検索対象の経度(世界測地系,10進数の度表記) |
datum | 任意 | 測地系 wgs:世界測地系(デフォルト) tky:日本測地系 |
output | 任意 | 出力形式:xml(デフォルト)/json |
callback | 任意 | JSONPとして出力する際のコールバック関数名を入力するためのパラメータ。UTF-8でエンコードした文字列を入力する。 |
解説:Yahoo!リバースジオコーダAPI
1347: /**
1348: * Yahoo!リバースジオコーダAPIを用いて緯度・経度から住所を求める
1349: * @param float $latitude 緯度(世界測地系,10進数の度表記)
1350: * @param float $longitude 経度(世界測地系,10進数の度表記)
1351: * @return array ['address'] フォーマット済み住所
1352: * ['city'] 市区町村
1353: * ['oaza'] 大字
1354: * ['aza'] 字
1355: * ['detail1'] 街区
1356: * FALSE=エラー
1357: */
1358: function getYOLP_Address($latitude, $longitude) {
1359: $appid = $this->YAHOO_APPLICATION_ID;
1360: $url = "https://map.yahooapis.jp/geoapi/V1/reverseGeoCoder?appid={$appid}&lat={$latitude}&lon={$longitude}&datum=wgs&output=xml";
1361: $this->webapi = $url;
1362:
1363: $res = array();
1364: $this->unknown_certificate();
1365: $xml = simplexml_load_file($url);
1366: //レスポンス・チェック
1367: if (isset($xml->Error)) {
1368: $this->error = TRUE;
1369: $this->errmsg = 'Yahoo!リバースジオコーダAPI - ' . $xml->Message;
1370: $res = FALSE;
1371: } else {
1372: $res['address'] = (string)$xml->Feature->Property->Address;
1373: foreach ($xml->Feature->Property->AddressElement as $val) {
1374: $res[(string)$val->Level] = (string)$val->Name;
1375: }
1376: }
1377:
1378: return $res;
1379: }
「HeartRails Geo API」による住所変換
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
URL |
---|
https://geoapi.heartrails.com/api/xml?method=searchByGeoLocation |
フィールド名 | 要否 | 内 容 |
---|---|---|
method | 必須 | メソッド名:searchByGeolocation(固定) |
y | 必須 | 検索対象の緯度(世界測地系,10進数の度表記) |
x | 必須 | 検索対象の経度(世界測地系,10進数の度表記) |
解説:HeartRails Geo API
1525: /**
1526: * HeartRails Geo API - 緯度経度による住所検索APIを用いて
1527: * 緯度・経度から住所を求める
1528: * @param float $latitude 緯度(世界測地系,10進数の度表記)
1529: * @param float $longitude 経度(世界測地系,10進数の度表記)
1530: * @return array ['address'] フォーマット済み住所
1531: * ['prefecture'] 都道府県名
1532: * ['city'] 市区町村名
1533: * ['town'] 町域名
1534: * ['postal'] 郵便番号
1535: * FALSE=エラー
1536: */
1537: function getHeartRailsGeo_Address($latitude, $longitude) {
1538: //リクエストURL
1539: $url = "https://geoapi.heartrails.com/api/xml?method=searchByGeoLocation&y={$latitude}&x={$longitude}";
1540: $this->webapi = $url;
1541:
1542: $res = array();
1543: $this->unknown_certificate();
1544: $xml = simplexml_load_file($url);
1545: //レスポンス・チェック
1546: if (isset($xml->error)) {
1547: $this->error = TRUE;
1548: $this->errmsg = 'HeartRails Geo API - ' . (string)$xml->error;
1549: $res = FALSE;
1550: } else if (! isset($xml->location)) {
1551: $this->error = TRUE;
1552: $this->errmsg = 'HeartRails Geo APIにトラブル発生';
1553: $res = FALSE;
1554: } else {
1555: foreach ($xml->location as $element) {
1556: $res['prefecture'] = (string)$element->prefecture;
1557: $res['city'] = (string)$element->city;
1558: $res['city_kana'] = (string)$element->{'city-kana'};
1559: $res['town'] = (string)$element->town;
1560: $res['town_kana'] = (string)$element->{'town-kana'};
1561: $res['postal'] = (string)$element->postal;
1562: $res['address'] = $res['prefecture'] . $res['city'] . $res['town'];
1563: break;
1564: }
1565: }
1566:
1567: return $res;
1568: }
「簡易逆ジオコーディングサービス」による住所変換
今回使う入力パラメータと出力結果のデータ構造を以下に示す。バージョン2の出力を利用する。
なお、入力する緯度・経度は世界測地系(wgs84)であることに留意されたい。
出典: 農研機構 (https://aginfo.cgk.affrc.go.jp/)
URL |
---|
https://aginfo.cgk.affrc.go.jp/ws/rgeocode.php |
フィールド名 | 要否 | 内 容 |
---|---|---|
key | 任意 | 出力文書のバージョン。現在は 1 または 2 のみ有効。省略時はバージョン 1。 |
lat | 必須 | 検索対象点の緯度(世界測地系,10進数の度表記) |
lon | 必須 | 検索対象点の経度(世界測地系,10進数の度表記) |
jsonp / php / ponp | 任意 | JSONP, PHPシリアライズ, PONP の各文書種別を指定する。いずれの指定も無い場合はXML文書を応答文書とする。 |
解説:簡易逆ジオコーディングサービス
0507: /**
0508: * 簡易逆ジオコーディングサービスのWebAPI URLを取得する
0509: * @param float $latitude 緯度(世界測地系,10進数の度表記)
0510: * @param float $longitude 経度(世界測地系,10進数の度表記)
0511: * @return string URL URL
0512: */
0513: function getURLrgeocode($latitude, $longitude) {
0514: return "https://aginfo.cgk.affrc.go.jp/ws/rgeocode.php?v=2&lat={$latitude}&lon={$longitude}";
0515: }
0517: /**
0518: * 簡易逆ジオコーディングサービスを用いて緯度・経度から住所を求める
0519: * @param float $latitude 緯度(世界測地系,10進数の度表記)
0520: * @param float $longitude 経度(世界測地系,10進数の度表記)
0521: * @return array (都道府県名,市町村名,町丁目,番地)/FALSE=エラー
0522: */
0523: function getAddress($latitude, $longitude) {
0524: //APIコール
0525: $url = $this->getURLrgeocode($latitude, $longitude);
0526: $this->webapi = $url;
0527:
0528: //PHP4用; DOM XML利用
0529: if ($this->isphp5over() == FALSE) {
0530: if (($dom = read_xml($url)) == NULL) return FALSE;
0531: $rgeocode = $dom->get_elements_by_tagname('rgeocode');
0532: //住所取得
0533: if (($result = $rgeocode[0]->get_elements_by_tagname('result')) == NULL) return FALSE;
0534: if (($pref = $result[0]->get_elements_by_tagname('prefecture')) == NULL) return FALSE;
0535: if (($pref2 = $pref[0]->get_elements_by_tagname('pname')) == NULL) return FALSE;
0536: $prefecture = $pref2[0]->get_content();
0537: $muni = $result[0]->get_elements_by_tagname('municipality');
0538: $municipality = '';
0539: if ($muni != NULL) {
0540: $muni2 = $muni[0]->get_elements_by_tagname('mname');
0541: if ($muni2 != NULL) $municipality = $muni2[0]->get_content();
0542: }
0543: $section = '';
0544: $homenumber = '';
0545: $loc = $result[0]->get_elements_by_tagname('local');
0546: if ($loc != NULL) {
0547: $loc2 = $loc[0]->get_elements_by_tagname('section');
0548: if ($loc2 != NULL) $section = $loc2[0]->get_content();
0549: $loc2 = $loc[0]->get_elements_by_tagname('homenumber');
0550: if ($loc2 != NULL) $homenumber = $loc2[0]->get_content();
0551: }
0552:
0553: //PHP5用; SimpleXML利用
0554: } else {
0555: $this->unknown_certificate();
0556: $rgeocode = simplexml_load_file($url);
0557: //レスポンス・チェック
0558: if (! isset($rgeocode->result)) {
0559: $this->error = TRUE;
0560: $this->errmsg = '簡易ジオコーディングサービス' . (isset($rgeocode->error) ? (' ' . $rgeocode->error) : 'にトラブル発生');
0561: return FALSE;
0562: }
0563: //住所取得
0564: $prefecture = $rgeocode->result->prefecture->pname;
0565: $municipality = $rgeocode->result->municipality->mname;
0566: $section = $rgeocode->result->local->section;
0567: $homenumber = $rgeocode->result->local->homenumber;
0568: }
0569:
0570: return array($prefecture, $municipality, $section, $homenumber);
0571: }
解説:検索と結果取得
2256: /**
2257: * 緯度・経度から住所を求める
2258: *
2259: * @param float $latitude 緯度(世界測地系,10進数の度表記)
2260: * @param float $longitude 経度(世界測地系,10進数の度表記)
2261: * @param int $api 0:Google Geocoding API
2262: * 1:Yahoo!リバースジオコーダAPI
2263: * 11:HeartRails Geo API
2264: * 21:簡易ジオコーディングサービス(省略時)
2265: * @return array ['address'] フォーマット済み住所
2266: * ['postalcode'] 郵便番号(一部サービスのみ)
2267: * ['prefecture'] 都道府県名
2268: * ['city'] 市町村名
2269: * ['local'] 市町村名以下
2270: * FALSE=エラー
2271: */
2272: function getAddress3($latitude, $longitude, $api=21) {
2273: $this->hits = 1;
2274: $ret = array();
2275: switch ($api) {
2276: //Google Geocoding API
2277: case 0:
2278: $res = $this->getGoogleAddress($latitude, $longitude);
2279: if ($res == FALSE) {
2280: $ret = FALSE;
2281: $this->error = TRUE;
2282: $this->errmsg = 'Google Geocoding APIにトラブル発生';
2283: $this->hits = 0;
2284: } else {
2285: $ret['address' ] = $res['address'];
2286: $ret['postalcode'] = preg_replace('/\-/', '', $res['postal_code']);
2287: $ret['prefecture'] = $res['administrative_area_level_1'];
2288: $ret['city'] = $res['locality'];
2289: $pat = '/' . $ret['prefecture'] . $ret['city'] . '(.+)$/ui';
2290: $ret['local'] = (preg_match($pat, $ret['address'], $arr) > 0) ?
2291: $arr[1] : '';
2292: }
2293: break;
2294: //Yahoo!リバースジオコーダAPI
2295: case 1:
2296: $res = $this->getYOLP_Address($latitude, $longitude);
2297: if ($res == FALSE) {
2298: $ret = FALSE;
2299: $this->hits = 0;
2300: } else {
2301: $ret['address' ] = isset($res['address']) ? $res['address'] : '';
2302: $ret['postalcode'] = '';
2303: $ret['prefecture'] = isset($res['prefecture']) ? $res['prefecture'] : '';
2304: $ret['city'] = isset($res['city']) ? $res['city'] : '';
2305: $pat = '/' . $ret['prefecture'] . $ret['city'] . '(.+)$/ui';
2306: $ret['local'] = (preg_match($pat, $ret['address'], $arr) > 0) ?
2307: $arr[1] : '';
2308: }
2309: break;
2310: //HeartRails Geo API
2311: case 11:
2312: $res = $this->getHeartRailsGeo_Address($latitude, $longitude);
2313: if ($res == FALSE) {
2314: $ret = FALSE;
2315: $this->hits = 0;
2316: } else {
2317: $ret['address' ] = isset($res['address']) ? $res['address'] : '';
2318: $ret['postalcode'] = isset($res['postal']) ? $res['postal'] : '';
2319: $ret['prefecture'] = isset($res['prefecture']) ? $res['prefecture'] : '';
2320: $ret['city'] = isset($res['city']) ? $res['city'] : '';
2321: $pat = '/' . $ret['prefecture'] . $ret['city'] . '(.+)$/ui';
2322: $ret['local'] = (preg_match($pat, $ret['address'], $arr) > 0) ?
2323: $arr[1] : '';
2324: }
2325: break;
2326: //簡易逆ジオコーディングサービス
2327: case 21:
2328: $arr = $this->getAddress($latitude, $longitude);
2329: if ($arr == FALSE) {
2330: $ret = FALSE;
2331: $this->hits = 0;
2332: } else {
2333: $ret['address'] = $arr[0] . $arr[1] . $arr[2] . $arr[3];
2334: $ret['postalcode'] = '';
2335: $ret['prefecture'] = $arr[0];
2336: $ret['city'] = $arr[1];
2337: $ret['local'] = $arr[2] . $arr[3];
2338: }
2339: break;
2340: //エラー
2341: default:
2342: $ret = FALSE;
2343: $this->error = TRUE;
2344: $this->errmsg = '逆ジオコーダーAPIの指定ミス';
2345: $this->hits = 0;
2346: break;
2347: }
2348:
2349: return $ret;
2350: }
結果はAPIによらずに等しく使えるように、下記の要素をもつ配列として戻す。エラー時には FALSE を返す。
添 字 | 内 容 |
---|---|
address | フォーマット済み住所 |
postalcode | 郵便番号(一部サービスのみ) |
prefecture | 都道府県名 |
city | 市町村名 |
local | 市町村名以下 |
解説:緯度・経度を正規化
0177: /**
0178: * 緯度・経度を正規化する
0179: * @param string $maps 緯度・経度表記
0180: * 緯度(小数),経度(小数)
0181: * N緯度(度.分.秒)E(度.分.秒)
0182: * N緯度(度/分/秒)E(度/分/秒)
0183: * @param string $ingeo 入力データの測地系(tokyo:日本測地系,wgs84:世界測地系)
0184: * @param string $outgeo出力データの測地系(tokyo:日本測地系,wgs84:世界測地系)
0185: * @param object $pgc pahooGeoCodeクラス
0186: * @return array (緯度,経度)/FALSE=エラー
0187: */
0188: function parseGeo($maps, $ingeo, $outgeo, $pgc) {
0189: //各種表記形式を小数表記に統一する
0190: if (preg_match('/([0-9\.\+\-]+)[\,\/ ]+([0-9\.\+\-]+)/', $maps, $arr) > 0) {
0191: $v1 = $arr[1];
0192: $v2 = $arr[2];
0193: } else if (preg_match('/N([0-9]+)[\.\/]([0-9]*)[\.\/]?([0-9]*)[\.\/]?([0-9]*)/', $maps, $arr) > 0) {
0194: $v1 = $arr[1];
0195: if (isset($arr[2])) $v1 += ($arr[2] / 60);
0196: if (isset($arr[3])) $v1 += ($arr[3] / 3600);
0197: if (isset($arr[4])) $v1 += ($arr[4] / 36000);
0198: if (preg_match('/E([0-9]+)[\.\/]([0-9]*)[\.\/]?([0-9]*)[\.\/]?([0-9]*)/', $maps, $arr) > 0) {
0199: $v2 = $arr[1];
0200: if (isset($arr[2])) $v2 += ($arr[2] / 60);
0201: if (isset($arr[3])) $v2 += ($arr[3] / 3600);
0202: if (isset($arr[4])) $v2 += ($arr[4] / 36000);
0203: }
0204: }
0205:
0206: //2桁の方を緯度、3桁の方を経度に
0207: if ($v1 < 100) $latitude = $v1;
0208: else if ($v1 >= 100) $longitude = $v1;
0209: if ($v2 < 100) $latitude = $v2;
0210: else if ($v2 >= 100) $longitude = $v2;
0211:
0212: //測地系の変換
0213: if (preg_match('/tokyo/i', $ingeo) > 0) {
0214: if (preg_match('/wgs84/i', $outgeo) > 0) {
0215: list($latitude, $longitude) = $pgc->tokyo_wgs84($latitude, $longitude);
0216: }
0217: } else if (preg_match('/wgs84/i', $ingeo) > 0) {
0218: if (preg_match('/tokyo/i', $outgeo) > 0) {
0219: list($latitude, $longitude) = $pgc->wgs84_tokyo($latitude, $longitude);
0220: }
0221: }
0222:
0223: return array($latitude, $longitude);
0224: }
(2021年9月22日)PHP8対応,リファラ・チェック改良