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

(1/1)
GoogleYahoo!JAPANHeartRailsOSM Nominatim国土地理院ジオコーディングAPI のいずれかのジオコーディングサービスを利用し、住所や駅名などのランドマークから緯度・経度を求めるPHPプログラムをつくり、流用できるようにクラス化する。
これらサービスのうち Google は全世界を対象として最も精度の高い検索が可能だが、有料サービスに移行したため(無料枠あり)、その他の無料利用できるサービスを選択できるようにしている。
ぱふぅ家のホームページで紹介するプログラムの住所検索機能は、すべてこのクラス pahooGeoCode を流用している。

(2023年7月11日)緯度・経度の小数点以下を指定桁数で丸めて表示できるようにした.検索キーの最小・最大長の指定ができるようにした.
(2023年7月2日)国土地理院ジオコーディングAPIを利用できるようにした.

目次

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

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

サンプル・プログラム

圧縮ファイルの内容
address2geo.phpサンプル・プログラム本体。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
address2geo.php 更新履歴
バージョン 更新日 内容
5.5.0 2023/07/11 緯度・経度の小数点以下を指定桁数で丸め表示,検索キーの最小・最大長の指定
5.4.0 2023/07/02 国土地理院ジオコーディングAPIを追加
5.3 2021/09/18 PHP8対応,リファラチェック改良,https化
5.2 2020/03/21 OSM Nominatim Search API追加
5.1 2019/05/08 HeartRails Geo API追加
pahooGeoCode.php 更新履歴
バージョン 更新日 内容
6.3.1 2023/07/09 bug-fix
6.3.0 2023/07/02 getPointsGSI()追加
6.2.0 2023/07/02 ip2address()追加
6.1.0 2022/12/30 ip2address()追加
6.0.4 2022/12/13 PHP8.2対応
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.3.0 2023/07/11 roundFloat() 追加
1.2.0 2023/04/22 exitIfLessVersion() 追加
1.1.2 2023/02/05 validString() 修正
1.11 2022/07/03 isCommandLine() 修正
1.1 2022/06/04 getValidString() 修正

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

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

準備:pahooGeoCode クラス

  37: class pahooGeoCode {
  38:     var $items;     //検索結果格納用
  39:     var $error;     //エラー・フラグ
  40:     var $errmsg;    //エラー・メッセージ
  41:     var $hits;      //検索ヒット件数
  42:     var $webapi;    //直前に呼び出したWebAPI URL
  43: 
  44:     //Google Cloud Platform APIキー
  45:     //https://cloud.google.com/maps-platform/
  46:     //※Google Maps APIを利用しないのなら登録不要
  47:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
  48:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
  49: 
  50:     //Yahoo! JAPAN Webサービス アプリケーションID
  51:     //https://e.developer.yahoo.co.jp/register
  52:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
  53:     var $YAHOO_APPLICATION_ID = '*****************************';
  54: 
  55:     //ジオどすII APIキー【廃止】
  56:     //http://geodosu.com/user/register
  57:     var $APIKEY_GEODOS = '*****************';
  58: 
  59:     //IP2Location.io APIキー
  60:     //https://www.ip2location.io/
  61:     //※IP2Location.ioを利用しないのなら登録不要
  62:     var $IP2LOCATION_API_KEY = '*****************';

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

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

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

  58: //住所検索サービスの選択
  59: //    0:Google
  60: //    1:Yahoo!ジオコーダAPI
  61: //   11:HeartRails Geo API
  62: //   12:OSM Nominatim Search API
  63: //   13:国土地理院ジオコーディングAPI
  64: define('GEOSERVICE', 13);

住所検索に使うクラウドサービスは、GoogleYahoo!JAPANHeartRails Geo APIOSM Nominatim Search API国土地理院ジオコーディングAPIから選べる。あらかじめ、定数 GEOSERVICE に値を設定すること。
住所検索サービスの制約
サービス名 制 約
0 Google 有料(決められた無料枠あり)。全世界の住所、ランドマークの検索可能。
1 Yahoo!JAPAN 無料(?)。住所、ランドマーク、海外のどれを検索するか指定。ランドマークや海外地名検索はGoogleに劣る。
11 HeartRails Geo API 無料。住所、または住所の一部のみ検索可能。
12 OSM Nominatim Search API 無料。全世界の住所、ランドマークの検索可能。精度はGoogleに劣る。
13 国土地理院ジオコーディングAPI 無料。日本国内の住所、ランドマークの検索可能。精度はGoogleに劣る。

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

Google Geocoding API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)はXMLなどで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
クラウドサービスのURL
URL
https://maps.googleapis.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

 185: /**
 186:  * 指定した検索キーワードからGoogleMaps API Geocoding(V3) のURLを取得する.
 187:  * @param   string $query 検索キーワード(UTF-8)
 188:  * @return  string URL URL
 189: */
 190: function getURL_GeoCodeAPI_V3($query) {
 191:     $key = $this->GOOGLE_API_KEY_2;
 192:     return "https://maps.googleapis.com/maps/api/geocode/xml?key={$key}&language=ja&region=JP&address=" . urlencode($query);
 193: }

 195: /**
 196:  * 指定した検索キーワードの緯度・経度を求める.
 197:  * クラウドサービスとしてGoogle Geocoding API(V3) を利用する.
 198:  * 検索結果結果は複数になることがあり,配列$itemsに格納する.
 199:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-08-01.shtm
 200:  * @param   string $query 検索キーワード
 201:  * @param   array  $items 情報を格納する配列
 202:  * @return  int ヒットした施設数/(-1):API呼び出し失敗
 203: */
 204: function getPointsV3_all($query, &$items) {
 205:     $url = $this->getURL_GeoCodeAPI_V3($query); //リクエストURL
 206:     $this->webapi = $url;
 207:     $n = 1;
 208: 
 209: //PEAR::XML_Unserializer
 210:     if (class_exists('XML_Unserializer')) {
 211:         $xml = new XML_Unserializer();
 212:         $xml_data = file_get_contents($url);
 213:         $xml->unserialize($xml_data);
 214:         $arr = $xml->getUnserializedData();
 215:         //レスポンス・チェック
 216:         if (preg_match('/ok/i', $arr['status']) == 0)   return (-1);
 217:         //位置情報
 218:         if (isset($arr['result']['geometry'])) {
 219:             $items[$n]['latitude']  = $arr['result']['geometry']['location']['lat'];
 220:             $items[$n]['longitude'] = $arr['result']['geometry']['location']['lng'];
 221:             $items[$n]['address']   = $this->trimAddress($arr['result']['formatted_address']);
 222:             $n++;
 223:         } else {
 224:             foreach ($arr['result'as $val) {
 225:                 $items[$n]['latitude']  = $val['geometry']['location']['lat'];
 226:                 $items[$n]['longitude'] = $val['geometry']['location']['lng'];
 227:                 $items[$n]['address']   = $this->trimAddress($val['formatted_address']);
 228:                 $n++;
 229:             }
 230:         }
 231:         $xml = NULL;
 232: 
 233: //PHP4用; DOM XML利用
 234:     } else if ($this->isphp5over() == FALSE) {
 235:         if (($dom = $this->read_xml($url)) == NULLreturn FALSE;
 236:         $gr = $dom->get_elements_by_tagname('GeocodeResponse');
 237:         //レスポンス・チェック
 238:         $res  = $gr[0]->get_elements_by_tagname('status');
 239:         if (preg_match("/ok/i", $res[0]->get_content()) == 0)   return 0;
 240:         //位置情報
 241:         $res = $gr[0]->get_elements_by_tagname('result');
 242:         foreach ($res as $val) {
 243:             $geo = $val->get_elements_by_tagname('geometry');
 244:             $loc = $geo[0]->get_elements_by_tagname('location');
 245:             $lat = $loc[0]->get_elements_by_tagname('lat');
 246:             $items[$n]['latitude'] = (double)$lat[0]->get_content();
 247:             $lng = $loc[0]->get_elements_by_tagname('lng');
 248:             $items[$n]['longitude'] = (double)$lng[0]->get_content();
 249:             $addr = $val->get_elements_by_tagname('formatted_address');
 250:             $items[$n]['address'] = $this->trimAddress((string)$addr[0]->get_content());
 251:             $n++;
 252:         }
 253: //PHP5用; SimpleXML利用
 254:     } else {
 255:         $this->unknown_certificate();
 256:         $res = simplexml_load_file($url);
 257:         //レスポンス・チェック
 258:         if (preg_match("/ok/i", $res->status) == 0)     return 0;
 259:         foreach ($res->result as $element) {
 260:             $items[$n]['latitude']  = (double)$element->geometry->location->lat;
 261:             $items[$n]['longitude'] = (double)$element->geometry->location->lng;
 262:             $items[$n]['address']   = $this->trimAddress((string)$element->formatted_address);
 263:             $n++;
 264:         }
 265:     }
 266:     return ($n - 1);
 267: }

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

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

YOLPコンテンツジオコーダAPI」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)はXMLなどで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
クラウドサービスの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

1250: /**
1251:  * 指定した検索キーワードからYOLPコンテンツジオコーダAPIのURLを取得する.
1252:  * @param   string $query 検索キーワード(UTF-8)
1253:  * @param   string $category 検索対象カテゴリ
1254:  *                           address  = 住所(省略時)
1255:  *                           landmark = ランドマーク
1256:  *                           world    = 世界
1257:  * @return  string URL リクエストURL
1258: */
1259: function getURL_YOLP_GeoCoder($query, $category='address') {
1260:     $appid = $this->YAHOO_APPLICATION_ID;
1261:     return "https://map.yahooapis.jp/geocode/cont/V1/contentsGeoCoder?appid={$appid}&el=UTF-8&output=xml&category={$category}&query=" . urlencode($query);
1262: }

1264: /**
1265:  * 指定した検索キーワードの緯度・経度を求める.
1266:  * クラウドサービスとしてYOLPコンテンツジオコーダAPIを利用する.
1267:  * $categoryに検索対象カテゴリをセットする(省略時は'address').
1268:  * 検索結果結果は複数になることがあり,配列$itemsに格納する.
1269:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-08-01.shtm
1270:  * @param   string $query 検索キーワード
1271:  * @param   array  $items 情報を格納する配列
1272:  * @param   string $category 検索対象カテゴリ
1273:  *                           address  = 住所(省略時)
1274:  *                           landmark = ランドマーク
1275:  *                           world    = 世界
1276:  * @return  int ヒットした施設数/(-1):API呼び出し失敗
1277: */
1278: function getPointsYOLP_all($query, &$items, $category='address') {
1279:     $url = $this->getURL_YOLP_GeoCoder($query, $category);  //リクエストURL
1280:     $this->webapi = $url;
1281:     $n = 1;
1282: 
1283:     $this->unknown_certificate();
1284:     $res = simplexml_load_file($url);
1285:     //レスポンス・チェック
1286:     if (!isset($res->ResultInfo))   return (-1);
1287:     if (isset($res->ResultInfo->Total&& ((int)$res->ResultInfo->Total <0))   return 0;       //v.5.73追加
1288: 
1289:     foreach ($res->Feature as $element) {
1290:         if (preg_match('/([\-0-9\.]+)\,([\-0-9\.]+)/i', $element->Geometry->Coordinates, $arr> 0) {
1291:             if (isset($arr[1]) && isset($arr[2])) {
1292:                 $items[$n]['latitude']  = (double)$arr[2];
1293:                 $items[$n]['longitude'] = (double)$arr[1];
1294:                 $items[$n]['address']   = (string)$element->Property->Address;
1295:                 $n++;
1296:             }
1297:         }
1298:     }
1299: 
1300:     return ($n - 1);
1301: }

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

1303: /**
1304:  * YOLPコンテンツジオコーダAPI のカテゴリ選択ラジオボタンの生成
1305:  * @param   string $name    HTML name
1306:  * @param   string $default デフォルト値(省略可能)
1307:  * @return  string HTML
1308: */
1309: function makeYOLP_GeoSelectCategory($name, $default='') {
1310:     //デフォルト値設定
1311:     if (isset($this->YOLP_GeoCategory[$default])) {
1312:         foreach ($this->YOLP_GeoCategory as $key=>$item) {
1313:             $item[$key]['checked'] = '';
1314:         }
1315:         $this->YOLP_GeoCategory[$default]['checked'] = 'checked';
1316:     }
1317:     //選択ラジオボタンの生成
1318:     $html = '';
1319:     $i = 1;
1320:     foreach ($this->YOLP_GeoCategory as $key=>$val) {
1321:         $html ."<input type=\"radio\" name=\"{$name}\" value=\"{$key}\" {$val['checked']} />{$val['title']} ";
1322:         $i++;
1323:     }
1324:     return $html;
1325: }

1335: /**
1336:  * checkedされているカテゴリを検索する
1337:  * @return  string 選択された関数名/FALSE=checkedされている処理がない
1338: */
1339: function getYOLP_GeoSelectCategory() {
1340:     foreach ($this->YOLP_GeoCategory as $key=>$val) {
1341:         if ($val['checked'] == 'checked')   return $key;
1342:     }
1343: 
1344:     return FALSE;
1345: }

1347: /**
1348:  * カテゴリをchekedする
1349:  * @param   string $val カテゴリ値
1350:  * @return  bool TRUE/FALSE
1351: */
1352: function setYOLP_GeoSelectCategory($val) {
1353:     $old = $this->getYOLP_GeoSelectCategory();
1354:     if ($val !FALSE)  $this->YOLP_GeoCategory[$old]['checked'] = '';
1355:     $this->YOLP_GeoCategory[$val]['checked'] = 'checked';
1356: 
1357:     return TRUE;
1358: }

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

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

HeartRails Geo API - キーワードによる住所検索 API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)はXMLなどで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。
得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
クラウドサービスのURL
URL
https://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

1494: /**
1495:  * 指定した検索キーワードの緯度・経度を求める.
1496:  * クラウドサービスとしてHeartRails Geo APIの住所検索APIを利用する.
1497:  * 検索方式は$matchingにセットする(省略時は'like').
1498:  * 検索結果結果は複数になることがあり,配列$itemsに格納する.
1499:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-08-01.shtm
1500:  * @param   string $query 検索キーワード:住所のみ(UTF-8)
1501:  * @param   array  $items 情報を格納する配列
1502:  * @param   string $matching 検索方式
1503:  *                             prefix = 前方一致
1504:  *                             like   = 部分一致(省略時)
1505:  *                             suffix = 後方一致
1506:  * @return  int ヒットした地点数/(-1):API呼び出し失敗
1507: */
1508: function getPointsHeartRailsGeo_all($query, &$items, $matching='like') {
1509:     //リクエストURL
1510:     $url = "https://geoapi.heartrails.com/api/xml?method=suggest&matching={$matching}&keyword=" . urlencode($query);
1511:     $this->webapi = $url;
1512:     $n = 1;
1513: 
1514:     $this->unknown_certificate();
1515:     $res = simplexml_load_file($url);
1516:     //レスポンス・チェック
1517:     if (isset($res->error)) {
1518:         $this->error = TRUE;
1519:         $this->errmsg = (string)$res->error;
1520:         $n = 0;
1521:     } else if (! isset($res->location)) {
1522:         $this->error = TRUE;
1523:         $this->errmsg = 'Not found';
1524:         $n = 1;
1525:     } else {
1526:         foreach ($res->location as $element) {
1527:             $items[$n]['latitude']   = (double)$element->y;
1528:             $items[$n]['longitude']  = (double)$element->x;
1529:             $items[$n]['prefecture'] = (string)$element->prefecture;
1530:             $items[$n]['city']       = (string)$element->city;
1531:             $items[$n]['city_kana']  = (string)$element->{'city-kana'};
1532:             $items[$n]['town']       = (string)$element->town;
1533:             $items[$n]['town_kana']  = (string)$element->{'town-kana'};
1534:             $items[$n]['postal']     = (string)$element->postal;
1535:             $items[$n]['address']    = (string)$element->prefecture . (string)$element->city . (string)$element->town;
1536:             $n++;
1537:         }
1538:     }
1539: 
1540:     return ($n - 1);
1541: }

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

「OSM Nominatim Search API」による緯度・経度変換

OSM Nominatim Search API」は、誰でも自由に地図を使えるよう、みんなでオープンデータの地理情報を作る「OpenStreetMap」(OSM)プロジェクトに含まれるクラウドサービスで、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)はXMLなどで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
クラウドサービスのURL
URL
https://nominatim.openstreetmap.org/search

入力パラメータ
フィールド名 要否 内  容
format 任意 出力形式。html|xml|json|jsonv2。省略時はhtml
q 必須 住所やランドマーク(UTF-8)
json_callback 任意 json の出力をラップするコールバック関数(JSONP)
addressdetails 任意 住所の要素への細分化を含むかどうか。0|1。省略時は0
2020年(令和2年)3月現在、APIコール時に User-Agent を含める必要がある。
応答データ構造(json) place_id ID lon 経度(世界測値系) lat 緯度(世界測値系) display_name 住所,郵便番号など license ライセンス

解説:OSM Nominatim Search API

1734: /**
1735:  * 指定した検索キーワードの緯度・経度を求める.
1736:  * クラウドサービスとしてOSM Nominatim Search API住所検索APIを利用する.
1737:  * 検索結果結果は複数になることがあり,配列$itemsに格納する.
1738:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-08-01.shtm
1739:  * @param   string $query 検索キーワード:住所のみ(UTF-8)
1740:  * @param   array  $items 情報を格納する配列
1741:  * @return  int ヒットした地点数/(-1):API呼び出し失敗
1742: */
1743: function getPointsNominatim_all($query, &$items) {
1744:     //リクエストURL
1745:     $url = 'https://nominatim.openstreetmap.org/search?format=json&q=' . urlencode($query);
1746:     $this->webapi = $url;
1747:     $n = 1;
1748: 
1749:     //User-Agent偽装
1750:     $header = array(
1751:         'Content-Type: application/x-www-form-urlencoded',
1752:         'User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1'
1753:     );
1754:     $stream = stream_context_create(array(
1755:         'http' => array(
1756:             'method' => 'GET',
1757:             'header' => implode("\r\n", $header),
1758:             'ignore_errors'=>TRUE
1759:         )
1760:     ));
1761:     $json = file_get_contents($url, FALSE, $stream);
1762: 
1763:     //レスポンス・チェック
1764:     if ($json == FALSE) {
1765:         $this->error = TRUE;
1766:         $this->errmsg = '';
1767:         $n = 0;
1768:     } else {
1769:         if ($this->isphp7over()) {
1770:             $res = @json_decode($json, FALSE, 512, JSON_BIGINT_AS_STRING);
1771:         } else {
1772:             $res = @json_decode($json, FALSE, 512);
1773:         }
1774:         foreach ($res as $element) {
1775:             $items[$n]['latitude']   = (double)$element->lat;
1776:             $items[$n]['longitude']  = (double)$element->lon;
1777:             $items[$n]['address']    = (string)$element->display_name;
1778:             $n++;
1779:         }
1780:     }
1781:     if ($n == 1) {
1782:         $this->error = TRUE;
1783:         $this->errmsg = 'Not found';
1784:     }
1785: 
1786:     return ($n - 1);
1787: }

出力形式は JSON を選択した。
クラウドサービスの呼び出し時、User-Agent を設定する目的で、 stream_context_create  関数を利用し、 file_get_contents  関数を呼び出している。
 json_decode  関数の呼び出しは、BIGINTの扱いが異なるPHP 7以上と未満で場合分けしている。
検索結果が複数ある場合に備え、緯度・経度・人間が読める住所のセットを、連想配列のプロパティ $items に格納しておく。

「国土地理院ジオコーディングAPI」による緯度・経度変換

国土地理院ジオコーディングAPI]」は、国土地理院が提供するクラウドサービスで、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)はJSONで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
なお、国土地理院によると、この機能は主に地理院地図からの利用を想定しているものであり、必ずしも常にまた長期的に提供できるとは限らないし、仕様や利用方法が予告なく変更する場合があるとアナウンスしている。
クラウドサービスのURL
URL
https://msearch.gsi.go.jp/address-search/AddressSearch

入力パラメータ
フィールド名 要否 内  容
q 必須 住所やランドマーク(UTF-8)
応答データ構造(json) _ geometry coordinates _0 経度 _1 緯度 properties addressCode 郵便番号 title 住所 _ geometry coordinates _0 経度 _1 緯度 properties addressCode 郵便番号 title 住所

解説:国土地理院ジオコーディングAPI

1790: /**
1791:  * 指定した検索キーワードの緯度・経度を求める.
1792:  * クラウドサービスとして国土地理院ジオコーディングAPIを利用する.
1793:  * 検索方式は$matchingにセットする(省略時は'like').
1794:  * 検索結果結果は複数になることがあり,配列$itemsに格納する.
1795:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-08-01.shtm
1796:  * @param   string $query 検索キーワード:住所のみ(UTF-8)
1797:  * @param   array  $items 情報を格納する配列
1798:  * @return  int ヒットした地点数/(-1):API呼び出し失敗
1799: */
1800: function getPointsGSI($query, &$items) {
1801:     //リクエストURL
1802:     $url = "https://msearch.gsi.go.jp/address-search/AddressSearch?q=" . urlencode($query);
1803:     $this->webapi = $url;
1804:     $n = 1;
1805: 
1806:     $this->unknown_certificate();
1807:     $json = file_get_contents($url);
1808:     //レスポンス・チェック
1809:     if ($json == FALSE) {
1810:         $this->error = TRUE;
1811:         $this->errmsg = '国土地理院ジオコーディングAPIが応答しません';
1812:         $n = 0;
1813:     } else {
1814:         $arr = json_decode($json);
1815:         if (count($arr) == 0) {
1816:             $this->error = TRUE;
1817:             $this->errmsg = '住所が見つかりません';
1818:             $n = 0;
1819:         } else {
1820:             foreach ($arr as $element) {
1821:                 $items[$n]['latitude']  = (double)$element->geometry->coordinates[1];
1822:                 $items[$n]['longitude'] = (double)$element->geometry->coordinates[0];
1823:                 $items[$n]['address']   = (string)$element->properties->title;
1824:                 $items[$n]['postal']    = (string)$element->properties->addressCode;
1825:                 $n++;
1826:             }
1827:         }
1828:     }
1829: 
1830:     return ($n - 1);
1831: }

検索結果が複数ある場合に備え、緯度・経度・住所のセットを、連想配列のプロパティ $items に格納していく。

解説:検索と結果取得

2198: /**
2199:  * 指定した検索キーワードの緯度・経度を求める.
2200:  * クラウドサービスとしてGoogle Geocoding API, Yahoo!ジオコーダAPI, 
2201:  * HeartRails Geo API, OSM Nominatim Search API,
2202:  * 国土地理院ジオコーディングAPIを指定できる.
2203:  * Yahoo!ジオコーダAPIを利用する場合は,検索対象カテゴリ$categoryをセットする.
2204:  * (省略時は 'like')
2205:  * 検索結果結果は複数になることがあり,配列$itemsに格納する.
2206:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-08-01.shtm
2207:  *
2208:  * @param   string $query 検索キーワード(UTF-8)
2209:  * @param   int    $api    0:Google Geocoding API(省略時)
2210:  *                         1:Yahoo!ジオコーダAPI
2211:  *                        11:HeartRails Geo API
2212:  *                        12:OSM Nominatim Search API
2213:  *                        13:国土地理院ジオコーディングAPI
2214:  * @param   string $category 検索対象カテゴリ(YOLPのみ必要)
2215:  *                           address  = 住所(省略時)
2216:  *                           landmark = ランドマーク
2217:  *                           world    = 世界
2218:  * @return  array(int ヒットした地点数,string WebAPI)
2219: */
2220: function searchPoint3($query, $api=0, $category='address') {
2221:     static $pat1 = '/E(\d+\.?\d*)N(\d+\.?\d*)/i';
2222:     static $pat2 = '/E(\d+)\.(\d+)\.(\d+)\.(\d+)N(\d+)\.(\d+)\.(\d+)\.(\d+)/i';
2223: 
2224:     unset($this->items);
2225:     $this->items = array();
2226:     $this->hits = 0;
2227: 
2228:     //緯度・経度表記(1)
2229:     if (preg_match($pat1, $query> 0) {
2230:         list($this->items[1]['latitude'], $this->items[1]['longitude']) =
2231:             $this->parse_geo($query);
2232:         $this->items[1]['address'] = '';
2233:         $this->hits = 1;
2234: 
2235:     //緯度・経度表記(2)
2236:     } else if (preg_match($pat2, $query> 0) {
2237:         list($this->items[1]['latitude'], $this->items[1]['longitude']) =
2238:             $this->parse_geo($query);
2239:         $this->items[1]['address'] = '';
2240:         $this->hits = 1;
2241: 
2242:     //Google Geocoding API使用
2243:     } else if ($api == 0) {
2244:         $n = $this->getPointsV3_all($query, $this->items);
2245:         if ($n == (-1)) {   //v.5.73修正
2246:             $this->error  = TRUE;
2247:             $this->errmsg = 'Google Geocoding APIにトラブル発生';
2248:             $this->hits = 0;
2249:         } else if ($n == 0) {
2250:             $this->error  = TRUE;
2251:             $this->errmsg = '指定キーワードでは検索できない';
2252:             $this->hits = 0;
2253:         } else {
2254:             $this->hits = $n;
2255:         }
2256: 
2257:     //Yahoo!ジオコーダAPI使用
2258:     } else if ($api == 1) {
2259:         $n = $this->getPointsYOLP_all($query, $this->items, $category);
2260:         if ($n == (-1)) {   //v.5.73修正
2261:             $this->error  = TRUE;
2262:             $this->errmsg = 'Yahoo!ジオコーダAPIにトラブル発生';
2263:             $this->hits = 0;
2264:         } else if ($n == 0) {
2265:             $this->error  = TRUE;
2266:             $this->errmsg = '指定キーワードでは検索できない';
2267:             $this->hits = 0;
2268:         } else {
2269:             $this->hits = $n;
2270:         }
2271: 
2272:     //HeartRails Geo API使用
2273:     } else if ($api == 11) {
2274:         $n = $this->getPointsHeartRailsGeo_all($query, $this->items, 'like');
2275:         if ($n == (-1)) {   //v.5.73修正
2276:             $this->hits = 0;
2277:         } else if ($n == 0) {
2278:             $this->errmsg = '指定キーワードでは検索できない';
2279:             $this->hits = 0;
2280:         } else {
2281:             $this->hits = $n;
2282:         }
2283: 
2284:     //OSM Nominatim Search API使用
2285:     } else if ($api == 12) {
2286:         $n = $this->getPointsNominatim_all($query, $this->items);
2287:         if ($n == (-1)) {
2288:             $this->errmsg = 'OSM Nominatim Search APIにトラブル発生';
2289:             $this->hits = 0;
2290:         } else if ($n == 0) {
2291:             $this->errmsg = '指定キーワードでは検索できない';
2292:             $this->hits = 0;
2293:         } else {
2294:             $this->hits = $n;
2295:         }
2296: 
2297:     //国土地理院ジオコーディングAPIs使用
2298:     } else if ($api == 13) {
2299:         $n = $this->getPointsGSI($query, $this->items);
2300:         if ($n > 0) {
2301:             $this->hits = $n;
2302:         }
2303: 
2304:     //エラー
2305:     } else {
2306:         $this->error  = TRUE;
2307:         $this->errmsg = 'ジオコーダーAPIの指定間違い';
2308:         $this->hits = 0;
2309:     }
2310: 
2311:     return array($this->hits, $this->webapi);
2312: }

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

 334: /**
 335:  * 検索結果(緯度・経度)を取得
 336:  * @param   int $id 取得したい地点番号
 337:  * @return  array(緯度,経度,住所):世界測地系
 338: */
 339: function getPoint($id) {
 340:     if ($id <0 || $id > $this->hits) {
 341:         $this->error  = TRUE;
 342:         $this->errmsg = '不正な地点番号';
 343:         $latitude  = FALSE;
 344:         $longitude = FALSE;
 345:         $address   = FALSE;
 346:     } else {
 347:         $this->error  = FALSE;
 348:         $this->errmsg = '';
 349:         $latitude  = $this->items[$id]['latitude'];
 350:         $longitude = $this->items[$id]['longitude'];
 351:         $address   = $this->items[$id]['address'];
 352:     }
 353: 
 354:     return array($latitude, $longitude, $address);
 355: }

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

解説:小数を指定した桁数で丸める

 437: /**
 438:  * 小数を指定した桁数で丸めて文字列として返す。
 439:  * 与えた小数の有効桁数より指定桁数が多いときには,末尾に0をサプレスする.
 440:  * @param   float $x  丸めたい小数
 441:  * @param   int   $dp 小数点以下の桁数(0:整数,負数:丸めない)
 442:  * @return  string 丸めた結果
 443: */
 444: function roundFloat($x, $dp) {
 445:     //丸めない
 446:     if ($dp < 0) {
 447:         $y = sprintf('%f', $x);
 448:     //整数に丸める
 449:     } else if ($dp == 0) {
 450:         $y = sprintf('%d', $x);
 451:     //指定した桁数に丸める
 452:     } else {
 453:         $ft = '%.' . (int)$dp . 'f';
 454:         $y = sprintf($ft, $x);
 455:     }
 456:     return (string)$y;
 457: }

緯度・経度の小数点以下の表示桁数を一定にするために、ユーザー定義関数 roundFloat を "pahooInputData.php" の中に用意した。
組み込み関数  sprintf  を利用し、小数を指定した桁数で丸めて文字列として返す。与えた小数の有効桁数より指定桁数が多いときには、末尾に0をサプレスする。

解説:メインプログラム

  66: //初期値
  67: define('DEF_QUERY', '東京都千代田区丸の内一丁目');      //検索キー
  68: define('DEF_CATEGORY', 'address');                      //カテゴリ
  69: 
  70: //表示幅(ピクセル)
  71: define('WIDTH',  600);
  72: 
  73: //緯度・経度の小数点以下表示桁数(0:整数に丸める,負数:丸めない)
  74: define('DECIMAL_POINT', 6);
  75: 
  76: //検索キーの最小文字長
  77: define('QUERY_MIN_LEN', 3);
  78: 
  79: //検索キーの最大文字長
  80: define('QUERY_MAX_LEN', 99);
  81: 
  82: //住所・緯度・経度に関わるクラス:include_pathに配置すること
  83: require_once('pahooGeoCode.php');

いくつかの初期値は定数として定義しており、自由に変更できる。

質疑応答

【質問】
国土地理院のジオコーディングはお試しになっていたと思いますが、なぜ利用されて居なかったのでしょうか? 精度でしょうか?(ヨッシーXX様,2023年6月30日)
【回答】
有用な情報をありがとうございます。試していませんでした。近いうちに試してみて、プログラムから利用できるようにしようと思います。

参考サイト

(この項おわり)
header