PHPで住所・ランドマークから緯度・経度を求める

(1/1)
GoogleYahoo!JAPANHeartRails のいずれかのジオコーディングサービスを利用し、住所や駅名などのランドマークから緯度・経度を求める PHP プログラムをつくる。
このあとで紹介していくプログラムの住所検索機能は、すべてこのプログラムを流用している。

(2019 年 5 月 8 日)HeartRails Geo API も利用できるようにした。

目次

サンプル・プログラムの実行例

PHPで住所・ランドマークから緯度・経度を求める

サンプル・プログラム

圧縮ファイルの内容
address2geo.phpサンプル・プログラム本体。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。

サンプル・プログラムの流れ

PHPで住所・ランドマークから緯度・経度を求める

準備:pahooGeoCode クラス

0035: class pahooGeoCode {
0036:     var $items;      //検索結果格納用
0037:     var $error;      //エラーフラグ
0038:     var $hits;       //検索ヒット件数
0039:     var $webapi; //直前に呼び出したWebAPI URL
0040: 
0041:     //Google Cloud Platform APIキー
0042:     //https://cloud.google.com/maps-platform/
0043:     //※Google Maps APIを利用しないのなら登録不要
0044:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
0045:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
0046: 
0047:     //Yahoo! JAPAN Webサービス アプリケーションID
0048:     //https://e.developer.yahoo.co.jp/register
0049:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
0050:     var $YAHOO_APPLICATION_ID = '*****************************';

地図描画や住所検索を行うために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHP でクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図や住所検索として Google を利用するのであれば、Google Cloud Platform API キー が必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を、Yahoo!JAPAN を利用するのであれば、Yahoo! JAPAN Web サービス アプリケーション IDが必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を、それぞれ参照されたい。

準備:住所検索サービスの選択

0031: //住所検索サービスの選択
0032: //    0:Google
0033: //    1:Yahoo!JAPAN
0034: //   11:HeartRails Geo API
0035: define('GEOSERVICE', 0);

住所検索に使う WebAPI は、GoogleYahoo!JAPANHeartRails Geo APIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
住所検索サービスの制約
サービス名 制 約
0 Google 有料(決められた無料枠あり)。
1 Yahoo!JAPAN 無料(?)。住所、ランドマーク、海外のどれを検索するか指定。ランドマークや海外地名検索はGoogleに劣る。
11 HeartRails Geo API 無料。住所、または住所の一部のみ検索可能。

「Google Geocoding API」による緯度・経度変換

Google Geocoding API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は XML などで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
WebAPIのURL
URL
http://maps.google.com/maps/api/geocode/xml

入力パラメータ
フィールド名 要否 内  容
key 必須 APIキー
address 必須 住所やランドマーク(UTF-8)
language 任意 使用言語。ja など
sensor 任意 true または false
2007 年(平成 19 年)10 月現在、検索キーワードには住所や駅名だけでなく、「東京ディスニーランド」「出雲大社」といったランドマークの指定もできるようになっている。
応答データ構造(xml) GeocodeResponse status ステータス result type 検索結果のタイプ formatted_address 人間が読むことができる住所 address_component long_name 正式名称 short_name 略称 type 検索結果のタイプ geometry location lat 緯度 lng 経度

解説:GoogleMaps API Geocoding

0163: /**
0164:  * GoogleMaps API Geocoding(V3) のURLを取得する
0165:  * @param string $query 検索キーワード(UTF-8)
0166:  * @return string URL URL
0167: */
0168: function getURL_GeoCodeAPI_V3($query) {
0169:     $key = $this->GOOGLE_API_KEY_2;
0170:     return "https://maps.googleapis.com/maps/api/geocode/xml?key={$key}&language=ja&region=JP&address=" . urlencode($query);
0171: }

0173: /**
0174:  * Google Geocoding API(V3) を用いて住所・駅名の緯度・経度を求める
0175:  * @param string $query 検索キーワード
0176:  * @param array  $items 情報を格納する配列
0177:  * @return int ヒットした施設数
0178: */
0179: function getPointsV3_all($query, &$items) {
0180:     $url = $this->getURL_GeoCodeAPI_V3($query);   //リクエストURL
0181:     $this->webapi = $url;
0182:     $n = 1;
0183: 
0184: //PEAR::XML_Unserializer
0185:     if (class_exists('XML_Unserializer')) {
0186:         $xml = new XML_Unserializer();
0187:         $xml_data = file_get_contents($url);
0188:         $xml->unserialize($xml_data);
0189:         $arr = $xml->getUnserializedData();
0190:         //レスポンス・チェック
0191:         if (preg_match('/ok/i', $arr['status']) == 0)   return 0;
0192:         //位置情報
0193:         if (isset($arr['result']['geometry'])) {
0194:             $items[$n]['latitude']  = $arr['result']['geometry']['location']['lat'];
0195:             $items[$n]['longitude'] = $arr['result']['geometry']['location']['lng'];
0196:             $items[$n]['address']   = $this->trimAddress($arr['result']['formatted_address']);
0197:             $n++;
0198:         } else {
0199:             foreach ($arr['result'] as $val) {
0200:                 $items[$n]['latitude']  = $val['geometry']['location']['lat'];
0201:                 $items[$n]['longitude'] = $val['geometry']['location']['lng'];
0202:                 $items[$n]['address']   = $this->trimAddress($val['formatted_address']);
0203:                 $n++;
0204:             }
0205:         }
0206:         $xml = NULL;
0207: 
0208: //PHP4用; DOM XML利用
0209:     } else if ($this->isphp5over() == FALSE) {
0210:         if (($dom = $this->read_xml($url)) == NULL)  return FALSE;
0211:         $gr = $dom->get_elements_by_tagname('GeocodeResponse');
0212:         //レスポンス・チェック
0213:         $res  = $gr[0]->get_elements_by_tagname('status');
0214:         if (preg_match("/ok/i", $res[0]->get_content()) == 0)  return 0;
0215:         //位置情報
0216:         $res = $gr[0]->get_elements_by_tagname('result');
0217:         foreach ($res as $val) {
0218:             $geo = $val->get_elements_by_tagname('geometry');
0219:             $loc = $geo[0]->get_elements_by_tagname('location');
0220:             $lat = $loc[0]->get_elements_by_tagname('lat');
0221:             $items[$n]['latitude'] = (double)$lat[0]->get_content();
0222:             $lng = $loc[0]->get_elements_by_tagname('lng');
0223:             $items[$n]['longitude'] = (double)$lng[0]->get_content();
0224:             $addr = $val->get_elements_by_tagname('formatted_address');
0225:             $items[$n]['address'] = $this->trimAddress((string)$addr[0]->get_content());
0226:             $n++;
0227:         }
0228: //PHP5用; SimpleXML利用
0229:     } else {
0230:         $this->unknown_certificate();
0231:         $res = simplexml_load_file($url);
0232:         //レスポンス・チェック
0233:         if (preg_match("/ok/i", $res->status) == 0)        return 0;
0234:         foreach ($res->result as $element) {
0235:             $items[$n]['latitude']  = (double)$element->geometry->location->lat;
0236:             $items[$n]['longitude'] = (double)$element->geometry->location->lng;
0237:             $items[$n]['address']   = $this->trimAddress((string)$element->formatted_address);
0238:             $n++;
0239:         }
0240:     }
0241:     return ($n - 1);
0242: }

WebAPI の呼び出しと、応答(XML データ)の処理は、これまでと同様である。
検索結果が複数ある場合に備え、緯度・経度・人間が読める住所のセットを、連想配列のプロパティ $items に格納しておく。

「YOLPコンテンツジオコーダAPI」による緯度・経度変換

YOLP コンテンツジオコーダ API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は XML などで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
WebAPIのURL
URL
https://map.yahooapis.jp/geocode/cont/V1/contentsGeoCoder

入力パラメータ
フィールド名 要否 内  容
appid 必須 アプリケーションID
query 必須 住所やランドマーク
ei 任意 文字エンコード:UTF-8(デフォルト)/EUC-JP/SJISなど
category 任意 検索対象カテゴリ:address(デフォルト)/landmark/world
results 任意 表示件数:最大10(デフォルト)
output 任意 出力形式:xml(デフォルト)/json
callback 任意 JSONPとして出力する際のコールバック関数名を入力するためのパラメータ。UTF-8でエンコードした文字列を入力する。
応答データ構造(xml) YDF ResultInfo Count データ件数 Total 全データ件数 Start 取得開始位置 Latency 応答時間 Status 処理結果(正常時200) Description 文字列解析結果 Copyright CompressType Feature Id ID Name 部分住所 Description 記載 Geometry Type 図形種別 Coordinates 経度,緯度 Property Genre ジャンルコード Query 検索クエリ Address 住所 AddressKana 住所読み AddressElement Name 部分住所

解説:YOLPコンテンツジオコーダAPI

1019: /**
1020:  * YOLPコンテンツジオコーダAPI のリクエストURLを取得する
1021:  * @param string $query 検索キーワード(UTF-8)
1022:  * @param string $category 検索対象カテゴリ
1023:  *                           address  = 住所(省略時)
1024:  *                           landmark = ランドマーク
1025:  *                           world    = 世界
1026:  * @return string URL リクエストURL
1027: */
1028: function getURL_YOLP_GeoCoder($query$category='address') {
1029:     $appid = $this->YAHOO_APPLICATION_ID;
1030:     return "https://map.yahooapis.jp/geocode/cont/V1/contentsGeoCoder?appid={$appid}&el=UTF-8&output=xml&category={$category}&query=" . urlencode($query);
1031: }

1033: /**
1034:  * YOLPコンテンツジオコーダAPI を用いて住所・駅名の緯度・経度を求める
1035:  * @param string $query 検索キーワード
1036:  * @param array  $items 情報を格納する配列
1037:  * @param string $category 検索対象カテゴリ
1038:  *                           address  = 住所(省略時)
1039:  *                           landmark = ランドマーク
1040:  *                           world    = 世界
1041:  * @return int ヒットした施設数
1042: */
1043: function getPointsYOLP_all($query, &$items$category='address') {
1044:     $url = $this->getURL_YOLP_GeoCoder($query$category);    //リクエストURL
1045:     $this->webapi = $url;
1046:     $n = 1;
1047: 
1048:     $this->unknown_certificate();
1049:     $res = simplexml_load_file($url);
1050:     //レスポンス・チェック
1051:     if (!isset($res->ResultInfo)) return 0;
1052:     foreach ($res->Feature as $element) {
1053:         if (preg_match('/([\-0-9\.]+)\,([\-0-9\.]+)/i', $element->Geometry->Coordinates$arr) > 0) {
1054:             if (isset($arr[1]) && isset($arr[2])) {
1055:                 $items[$n]['latitude']  = (double)$arr[2];
1056:                 $items[$n]['longitude'] = (double)$arr[1];
1057:                 $items[$n]['address']   = (string)$element->Property->Address;
1058:                 $n++;
1059:             }
1060:         }
1061:     }
1062: 
1063:     return ($n - 1);
1064: }

WebAPI の呼び出しと、応答(XML データ)の処理は、これまでと同様である。
検索結果が複数ある場合に備え、緯度・経度・住所のセットを、連想配列のプロパティ $items に格納しておく。

1066: /**
1067:  * YOLPコンテンツジオコーダAPI のカテゴリ選択ラジオボタンの生成
1068:  * @param string $name    HTML name
1069:  * @param string $default デフォルト値(省略可能)
1070:  * @return string HTML
1071: */
1072: function makeYOLP_GeoSelectCategory($name$default='') {
1073:     //デフォルト値設定
1074:     if (isset($this->YOLP_GeoCategory[$default])) {
1075:         foreach ($this->YOLP_GeoCategory as $key=>$item) {
1076:             $item[$key]['checked'] = '';
1077:         }
1078:         $this->YOLP_GeoCategory[$default]['checked'] = 'checked';
1079:     }
1080:     //選択ラジオボタンの生成
1081:     $html = '';
1082:     $i = 1;
1083:     foreach ($this->YOLP_GeoCategory as $key=>$val) {
1084:         $html .= "<input type=\"radio\" name=\"{$name}\" value=\"{$key}\" {$val['checked']} />{$val['title']} ";
1085:         $i++;
1086:     }
1087:     return $html;
1088: }

1098: /**
1099:  * checkedされているカテゴリを検索する
1100:  * @return string 選択された関数名/FALSE=checkedされている処理がない
1101: */
1102: function getYOLP_GeoSelectCategory() {
1103:     foreach ($this->YOLP_GeoCategory as $key=>$val) {
1104:         if ($val['checked'] == 'checked')    return $key;
1105:     }
1106: 
1107:     return FALSE;
1108: }

1110: /**
1111:  * カテゴリをchekedする
1112:  * @param string $val カテゴリ値
1113:  * @return bool TRUE/FALSE
1114: */
1115: function setYOLP_GeoSelectCategory($val) {
1116:     $old = $this->getYOLP_GeoSelectCategory();
1117:     if ($val != FALSE)   $this->YOLP_GeoCategory[$old]['checked'] = '';
1118:     $this->YOLP_GeoCategory[$val]['checked'] = 'checked';
1119: 
1120:     return TRUE;
1121: }

YOLP コンテンツジオコーダ API では、住所、ランドマーク、海外を指定する必要があり、これらを指定しやすくするためのユーザー関数を用意した。

「HeartRails Geo API」による緯度・経度変換

HeartRails Geo API - キーワードによる住所検索 API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は XML などで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
WebAPIのURL
URL
http://geoapi.heartrails.com/api/xml?method=suggest

入力パラメータ
フィールド名 要否 内  容
method 必須 メソッド名:suggest(固定)
keyword 必須 検索キーワード(UTF-8でURLエンコード)
matching 必須 prefix(前方一致)、like(部分一致)、suffix(後方一致)のいずれか
応答データ構造(xml) response location city 市区町村名 city-kana 市区町村名よみ(平仮名) town 町域名 town-kana 町域名よみ(平仮名) x 経度(世界測値系) y 緯度(世界測値系) prefecture 都道府県名 postal 郵便番号(ハイフンなし)

解説:HeartRails Geo API

1221: /**
1222:  * HeartRails Geo API - 住所検索APIを用いて住所の緯度・経度を求める
1223:  * @param string $query 検索キーワード:住所のみ(UTF-8)
1224:  * @param array  $items 情報を格納する配列
1225:  * @param string $matching 検索方式
1226:  *                             prefix = 前方一致
1227:  *                             like   = 部分一致(省略時)
1228:  *                             suffix = 後方一致
1229:  * @return int ヒットした地点数
1230: */
1231: function getPointsHeartRailsGeo_all($query, &$items$matching='like') {
1232:     $url = "http://geoapi.heartrails.com/api/xml?method=suggest&matching={$matching}&keyword=" . urlencode($query);     //リクエストURL
1233:     $this->webapi = $url;
1234:     $n = 1;
1235: 
1236:     $this->unknown_certificate();
1237:     $res = simplexml_load_file($url);
1238:     //レスポンス・チェック
1239:     if (isset($res->error)) {
1240:         $this->error = TRUE;
1241:         $this->errmsg = (string)$res->error;
1242:         $n = FALSE;
1243:     } else if (! isset($res->location)) {
1244:         $this->error = TRUE;
1245:         $this->errmsg = 'Not found';
1246:         $n = 0;
1247:     } else {
1248:         foreach ($res->location as $element) {
1249:             $items[$n]['latitude']  = (double)$element->y;
1250:             $items[$n]['longitude'] = (double)$element->x;
1251:             $items[$n]['address']   = (string)$element->prefecture . (string)$element->city . (string)$element->town;
1252:             $n++;
1253:         }
1254:     }
1255: 
1256:     return ($n - 1);
1257: }

WebAPI の呼び出しと、応答(XML データ)の処理は、これまでと同様である。
検索結果が複数ある場合に備え、緯度・経度・住所のセットを、連想配列のプロパティ $items に格納しておく。

解説:検索と結果取得

1449: /**
1450:  * ジオコーダAPI を用いて住所・ランドマークの緯度・経度を検索
1451:  *
1452:  * @param string $query 検索キーワード(UTF-8)
1453:  * @param string $api   0:Google Geocoding API(省略時)
1454:  *                        1:Yahoo!ジオコーダAPI
1455:  *                        2:HeartRails Geo API
1456:  * @param string $category 検索対象カテゴリ(YOLPのみ必要)
1457:  *                           address  = 住所(省略時)
1458:  *                           landmark = ランドマーク
1459:  *                           world    = 世界
1460:  * @return array(int ヒットした地点数,string WebAPI)
1461: */
1462: function searchPoint3($query$api=0, $category='address') {
1463:     static $pat1 = '/E(\d+)\.(\d+)\.(\d+)\.(\d+)N(\d+)\.(\d+)\.(\d+)\.(\d+)/i';
1464: 
1465:     unset($this->items);
1466:     $this->items = array();
1467:     $this->hits = 0;
1468: 
1469:     //緯度・経度表記
1470:     if (preg_match($pat1$query) > 0) {
1471:         list($this->items[1]['latitude'], $this->items[1]['longitude']) =
1472:             $this->parse_geo($query);
1473:         $this->items[1]['address'] = '';
1474:         $this->hits = 1;
1475: 
1476:     //Google Geocoding API使用
1477:     } else if ($api == 0) {
1478:         $n = $this->getPointsV3_all($query$this->items);
1479:         if ($n == FALSE) {
1480:             $this->error  = TRUE;
1481:             $this->errmsg = 'Google Geocoding APIにトラブル発生';
1482:             $this->hits = 0;
1483:         } else if ($n == 0) {
1484:             $this->error  = TRUE;
1485:             $this->errmsg = '検索結果がない';
1486:             $this->hits = 0;
1487:         } else {
1488:             $this->hits = $n;
1489:         }
1490: 
1491:     //Yahoo!ジオコーダAPI使用
1492:     } else if ($api == 1) {
1493:         $n = $this->getPointsYOLP_all($query$this->items$category);
1494:         if ($n == FALSE) {
1495:             $this->error  = TRUE;
1496:             $this->errmsg = 'Yahoo!ジオコーダAPIにトラブル発生';
1497:             $this->hits = 0;
1498:         } else if ($n == 0) {
1499:             $this->error  = TRUE;
1500:             $this->errmsg = '検索結果がない';
1501:             $this->hits = 0;
1502:         } else {
1503:             $this->hits = $n;
1504:         }
1505: 
1506:     //HeartRails Geo API使用
1507:     } else if ($api == 11) {
1508:         $n = $this->getPointsHeartRailsGeo_all($query$this->items, 'like');
1509:         if ($n == FALSE) {
1510:             $this->hits = 0;
1511:         } else if ($n == 0) {
1512:             $this->errmsg = '検索結果がない';
1513:             $this->hits = 0;
1514:         } else {
1515:             $this->hits = $n;
1516:         }
1517: 
1518:     //エラー
1519:     } else {
1520:         $this->error  = TRUE;
1521:         $this->errmsg = 'ジオコーダーAPIの指定間違い';
1522:         $this->hits = 0;
1523:     }
1524: 
1525:     return array($this->hits$this->webapi);
1526: }

メソッド searchPoint3 は、引数の形式に応じて、緯度・経度表記なら値を分離し、それ以外なら引数 $api の値によって、Google用メソッド getPointsV3_all、Yahoo!用メソッド getPointsYOLP_all、HeartRails Geo API用メソッド getPointsHeartRailsGeo_all を呼び出す。
結果はプロパティ $items に格納する。戻り値としてヒットした件数を返す。

プロパティ $items に格納された情報を取り出すには、メソッド getPoint を使う。
引数の地点番号は、1 から順に増える正の整数である。最大値は searchPoint3 の戻り値である。

参考サイト

(この項おわり)
header