JavaScriptでWebAPIを利用して住所から緯度・経度を求める

(1/1)
JavaScript ES6 がIE以外のブラウザで利用できるようになった。このバージョンではクラス宣言もできる。
そこで今回は、「PHPでGoogle等を利用して住所から緯度・経度を求める」で紹介したPHPサーバサイド・プラグラムを、JavaScriptを使ってクライアント・サイドに移植してみる。

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

Google Geocoding API を利用し、住所やランドマークを緯度・経度に変換する

サンプル・プログラムのダウンロード

圧縮ファイルの内容
address2geoサンプル・プログラム本体。
pahooGeoCode.js住所・緯度・経度に関わるクラス pahooGeoCode。

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

Google Geocoding API を利用し、住所やランドマークを緯度・経度に変換する
プログラムの流れは、冒頭で jQuery とクラス pahooGeoCode を読み込み、各種初期化を行う。
続いて、2つのボタン押下によって、各々 exec, clear のアクションを発生する。
execは、キーワードから緯度・経度を求めるもので、詳細は後述する。
clearは、初期化してからexecと同じ処理を行う。

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

Google Geocoding API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は JSON などで戻すという形である。今回使う入力パラメータと出力結果のデータ構造を以下に示す。得られる緯度・経度は世界測地系(wgs84)であることに留意されたい。
WebAPI
URL
https://maps.google.com/maps/api/geocode/json
入力パラメータ
フィールド名 要否 内  容
key 必須 Google APIを利用するためのアプリケーション・キー(後述)
address 必須 検索キーワード。住所、駅名、ランドマークなど(UTF-8)
language 省略可能 使用言語。【例】ja
region 省略可能 地域コード。【例】JP
bounds 省略可能 境界ボックスの南西と北東の角の緯度・経度で指定。【例】34.172684,-118.604794|34.236144,-118.500938
出力パラメータ (JSON)
フィールド名 内  容
results[] address_components[] long_name住所
short_name住所(短縮)
types[]住所の種類
formatted_address住所(宛先表記)
geometry location lat緯度
lng経度
location_type取得結果の精度
viewport southwestビューポートの南西の座標
northeastビューポートの北東の座標
bounds southwest境界ボックスの南西の座標
northeast境界ボックスの北東の座標
place_idPlace ID

準備、クラスについて

Google Geocoding API を利用するにはアプリケーション・キーが必要となる。
Google Maps Platform」にアクセスし、「使ってみる」ボタンをクリックすると無料で取得できる。
JavaScript ES6 では、class を使ってクラスを宣言できる。まず、クラス・ファイル "pahooGeoCode.js" を解説する。

  14: class pahooGeoCode {
  15: 
  16: /**
  17:  * コンストラクタ
  18:  * @param   Function callback コールバック関数
  19:  * @return  なし
  20: */
  21: constructor(callback) {
  22:     this.items     = new Array();       //検索結果格納用
  23:     this.pp        = new Object();      //各種パラメータ格納用
  24:     this.pp.error  = false;         //エラーフラグ
  25:     this.pp.errmsg = '';                //エラーメッセージ
  26:     this.pp.hits   = 0;             //検索ヒット件数
  27:     this.pp.webapi = '';
  28: 
  29:     //Google API KEY
  30:     //https://cloud.google.com/maps-platform/
  31:     this.GOOGLE_API_KEY = 'AIzaSyDX1hKen3YC3NrJ8HY1UTr75ZrI7vj6nQw';
  32: 
  33:     //Google Maps APIのスクリプト読み込み
  34:     var key = this.GOOGLE_API_KEY;
  35:     var fn = (typeof callback !'undefined'? '&callback=' + callback : '';
  36:     var url = 'https://maps.googleapis.com/maps/api/js?key=' + key + fn + '&region=JP';
  37:     var script = document.createElement('script');
  38:     script.src = url;
  39:     script.async = true;
  40:     document.head.appendChild(script);
  41: }

コンストラクタは constructor で定義する。
先ほど取得したアプリケーションキーをメンバ変数 GOOGLE_API_KEY に記入する。
クラス pahooGeoCodeGoogleMaps API を呼び出すとき、このアプリケーションを使う。
メンバ変数を用いるときは、このようにコンストラクタの中で初期化しておく。

後半は、GoogleMaps API のスクリプトを動的に読み込む処理である。コンストラクタに引数 callback が渡されていれば、これをコールバック関数としてAPIを呼び出す。

解説:Google Geocoding API

  44: /**
  45:  * Google Geocoding API(V3) のURLを取得する(JSON)
  46:  * @param   String query 検索キーワード(UTF-8)
  47:  * @return  String URL 呼び出しURL
  48: */
  49: getURL_GeoCodeAPI_V3(query) {
  50:     var key = this.GOOGLE_API_KEY;
  51:     var url = 'https://maps.googleapis.com/maps/api/geocode/json?key=' + key + '&language=ja&region=JP&address=' + encodeURI(query);
  52:     return url;
  53: }
  54: 
  55: /**
  56:  * Google Geocoding API(V3) を用いて住所・駅名の緯度・経度を求める
  57:  * @param   String query   検索キーワード
  58:  * @param   Array  items[] {latitude:緯度, longitude:経度, address:住所}
  59:  * @param   Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
  60:  *                    hits:ヒット件数,webapi:WebAPIのURL}
  61:  * @param   Function callback  コールバック関数(非同期処理用)
  62:  * @return  なし
  63: */
  64: getPointsV3_all(query, items, pp, callback) {
  65:     var url   = this.getURL_GeoCodeAPI_V3(query);   //リクエストURL
  66:     pp.webapi = url;
  67:     var n = 0;
  68:     var obj = new Object();
  69: 
  70:     //JSONデータ取得
  71:     var req = new XMLHttpRequest();
  72:     req.onreadystatechange = function() {
  73:         if (req.readyState == 4 && req.status == 200) {
  74:             pahooGeoCode.getPointsV3_callback(req.responseText, items, pp, callback);
  75:         }
  76:     };
  77:     try {
  78:         req.open('GET', url, true);
  79:         req.send(null);
  80:     } catch(e) {
  81:         pp.error  = true;
  82:         pp.errmsg = 'エラー:Google Geocoding APIが呼び出せない.';
  83:         pp.hits   = 0;
  84:         pahooGeoCode.getPointsV3_callback(req.responseText, items, pp, callback);
  85:     }
  86: }
  87: 
  88: /**
  89:  * Google Geocoding API(V3) のコールバック関数(非同期処理用)
  90:  * @param   String results APIのリターン値
  91:  * @param   Array  items[] {latitude:緯度, longitude:経度, address:住所}
  92:  * @param   Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
  93:  *                    hits:ヒット件数,webapi:WebAPIのURL}
  94:  * @param   Function callback  コールバック関数(非同期処理用)
  95:  * @return  なし
  96: */
  97: static getPointsV3_callback(results, items, pp, callback) {
  98:     if (pp.error == false) {
  99:         var n = 0;
 100:         var json = JSON.parse(results);
 101:         //レスポンス・チェック
 102:         if (json.status.match(/ok/i!null) {
 103:             json.results.forEach(function(val) {
 104:                 var obj = new Object();
 105:                 obj.latitude  = val.geometry.location.lat;
 106:                 obj.longitude = val.geometry.location.lng;
 107:                 obj.address   = val.formatted_address;
 108:                 items.push(obj);
 109:                 n++;
 110:             });
 111:             pp.error  = false;
 112:             pp.errmsg = '';
 113:             pp.hits   = n;
 114:         } else if (json.status.match(/ZERO_RESULTS/i!null) {
 115:             pp.error  = true;
 116:             pp.errmsg = 'エラー:結果がない.';
 117:             pp.hits   = 0;
 118:         } else {
 119:             pp.error  = true;
 120:             pp.errmsg = 'エラー:' + json.error_message;
 121:             pp.hits   = 0;
 122:         }
 123:     }
 124:     callback(items, pp);
 125: }

メソッド getURL_GeoCodeAPI_V3 は、前述のパラメータ表にもとづき、Google Geocoding API の呼び出しURLを求める。

メソッド getPointsV3_all は、Google Geocoding API の呼び出しを行う。呼び出しは XMLHttpRequest 関数によって行う。
XMLHttpRequest は非同期なデータ通信を実現するための関数である。非同期とはどういうことかというと、WebAPIが値を戻す間に、JavaScriptの処理は先へ進むということである。
WebAPIが何らかの反応を起こすと通信が発生し、onreadystatechange イベントが発生する。これをキャッチアップして、適切な処理を行うのがコールバック関数である。ここではメソッド getPointsV3_callback をコールバック関数としている。
逆に言うと、コールバック関数が呼び出されるまで、JSONには正しい値が入っていないと言うことである。
よって、JSONから必要なデータを取り込むのは、コールバック関数の方で行う。
さらに、getPointsV3_callback から引数で指定された callback 関数を呼び出す――ここでは、address2get.html の方に定義したユーザー関数 searchKeyword_callback をコールする――ことで、結果の一覧表作成とエラー表示までを行う。

なお、コールバック関数は“関数”でなければならないが、静的メソッドでも呼び出しが可能である。
ただし、静的メソッドであるため、ここからプロパティを参照できない。このため、データの受け渡しに、引数を使っている。

PHPは、原則としてシングルスレットであるから非同期であることをさほど意識しないで済むが、このようにJavaScriptでは非同期であることを意識し、イベント駆動式のコーディングを行わなければならない。

解説:Google Geocoding API(JS版)

 127: /**
 128:  * Google Geocoding API(V3) を用いて住所・駅名の緯度・経度を求める(JS版)
 129:  * @param   String query   検索キーワード
 130:  * @param   Array  items[] {latitude:緯度, longitude:経度, address:住所}
 131:  * @param   Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
 132:  *                    hits:ヒット件数,webapi:WebAPIのURL}
 133:  * @param   Function callback  コールバック関数(非同期処理用)
 134:  * @return  なし
 135: */
 136: getPointsV3_all_JS(query, items, pp, callback) {
 137:     const geocoder = new google.maps.Geocoder();
 138:     geocoder.geocode({
 139:         'address': query,
 140:         'region': 'jp'
 141:     },
 142:     function(results, status) {
 143:         //成功
 144:         if (status == google.maps.GeocoderStatus.OK) {
 145:             pahooGeoCode.getPointsV3_callback(results, items, pp, callback);
 146:         //エラー処理
 147:         } else if (status == google.maps.GeocoderStatus.ZERO_RESULTS) {
 148:             pp.error = true;
 149:             pp.errmsg = '結果がない.';
 150:             pahooGeoCode.getPointsV3_callback(results, items, pp, callback);
 151:         } else {
 152:             pp.error = true;
 153:             pp.errmsg = 'API呼び出しでトラブルが起きた.';
 154:             pahooGeoCode.getPointsV3_callback(results, items, pp, callback);
 155:         }
 156:     });
 157: }
 158: 
 159: /**
 160:  * Google Geocoding API(V3) のコールバック関数(JS版)
 161:  * @param   Array  results[] APIのリターン値
 162:  * @param   Array  items[] {latitude:緯度, longitude:経度, address:住所}
 163:  * @param   Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
 164:  *                    hits:ヒット件数,webapi:WebAPIのURL}
 165:  * @param   Function callback  コールバック関数(非同期処理用)
 166:  * @return  なし
 167: */
 168: static getPointsV3_callback_JS(results, items, pp, callback) {
 169:     var n = 0;
 170:     if (pp.error == false) {
 171:         results.forEach(function(val) {
 172:             var obj = new Object();
 173:             obj.latitude  = parseFloat(val.geometry.location.lat());
 174:             obj.longitude = parseFloat(val.geometry.location.lng());
 175:             obj.address   = val.formatted_address;
 176:             items.push(obj);
 177:             n++;
 178:         });
 179:         //エラー処理
 180:         if (n == 0) {
 181:             pp.error  = true;
 182:             pp.errmsg = 'エラー:検索結果がない';
 183:             pp.hits = 0;
 184:         } else {
 185:             pp.error  = false;
 186:             pp.errmsg = '';
 187:             pp.hits = n;
 188:         }
 189:     }
 190:     callback(items, pp);
 191: }

メソッド getPointsV3_all_JS およびコールバック関数 getPointsV3_callback_JS は、Google公式で紹介されているJavaScriptのコードを移植したものである。前述のAPIを直接呼び出すのと同じ作用をもつ。

解説:住所から緯度・経度を求める

 193: /**
 194:  * 緯度経度文字列を分解する
 195:  * @param   String str 緯度経度文字列
 196:  * @return  Object {longitude:緯度, latitude:経度}
 197: */
 198: parse_geo(str) {
 199:     var re = /E(\d+)\.(\d+)\.(\d+)\.(\d+)N(\d+)\.(\d+)\.(\d+)\.(\d+)/i;
 200:     var arr = str.match(re);
 201:     for (var i = 1i < parseInt(arr.length); i++) {
 202:         arr[i] = parseInt(arr[i]);
 203:     }
 204:     var res = {
 205:         longitude: arr[1+ arr[2] / 60 + arr[3] / 3600 + arr[4] / 36000,
 206:         latitude:  arr[5+ arr[6] / 60 + arr[7] / 3600 + arr[8] / 36000
 207:     }
 208:     return res;
 209: }
 210: 
 211: /**
 212:  * Google Geocoding API(V3) を用いて住所・駅名の緯度・経度を検索
 213:  * @param   String query 検索キーワード(UTF-8)
 214:  * @param   Function callback  コールバック関数
 215:  * @return  なし
 216: */
 217: searchPoint(query, callback) {
 218:     this.items = [];        //結果を空にする
 219:     this.hits = 0;
 220: 
 221:     //緯度・経度表記
 222:     var re = /E(\d+)\.(\d+)\.(\d+)\.(\d+)N(\d+)\.(\d+)\.(\d+)\.(\d+)/i;
 223:     if (query.match(re!null) {
 224:         var obj = new Object();
 225:         var res = this.parse_geo(query);
 226:         obj.latitude  = parseFloat(res.latitude);
 227:         obj.longitude = parseFloat(res.longitude);
 228:         obj.address   = '';
 229:         this.items.push(obj);
 230:         this.hits = 1;
 231:         callback(this.items);
 232: 
 233:     //Google Geocoding API使用
 234:     } else {
 235:         this.getPointsV3_all(query, this.items, this.pp, callback);     //検索実行
 236:     }
 237: }

メソッド parse_geo は、"E139.45.56.9N35.41.0.7"(東経 139度45分56.9秒、北緯 35度41分0.7秒)のようなテキストを解釈するためのもので、移植元のPHPプログラムをそのまま移植した。

メソッド searchPoint が、Google Geocoding API を利用し、住所やランドマーク名から緯度・経度を求める。

解説:初期化処理

次に、HTML本体 "address2geo.html" の解説をする。

  11: <!DOCTYPE html>
  12: <html lang="ja">
  13: <head>
  14: <meta charset="UTF-8">
  15: <title></title>
  16: <meta name="author" content="studio pahoo" />
  17: <meta name="copyright" content="studio pahoo" />
  18: <meta name="ROBOTS" content="NOINDEX,NOFOLLOW" />
  19: <meta http-equiv="pragma" content="no-cache">
  20: <meta http-equiv="cache-control" content="no-cache">
  21: <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.js"></script>
  22: <script src="./pahooGeoCode.js"></script>
  23: 
  24: <script>
  25: //初期設定
  26: $(function() {
  27:     //プログラム・タイトル
  28:     TITLE = '住所・ランドマークを緯度・経度に変換(JS版)';
  29: 
  30:     //初期値
  31:     DEF_QUERY = '東京駅';
  32: 
  33:     //クラス pahooGeoCode
  34:     PGC = new pahooGeoCode('searchKeyword');
  35: 
  36:     //画面の初期値設定
  37:     var refere = 'http://www.pahoo.org/e-soul/webtech/js01/js01-09-01.shtm';
  38:     document.title = TITLE;
  39:     $('#title').html(TITLE + '  <span style="font-size:small;">' + getLastModified() + '版</span>');
  40:     $('#refere').html('参考サイト:<a href="' + refere +'">' + refere + '</a>');
  41:     init();
  42: });
  43: 

Ajax と、作成したクラス "pahooGeoCode.js" を利用する。
Ajaxを使った初期設定では、デフォルト入力値 def_query、参照URL referer、タイトルなどを、各要素に代入する。

解説:住所・ランドマークを緯度・経度に変換

  65: /**
  66:  * 住所・ランドマーク検索
  67:  * @param   なし
  68:  * @return  なし
  69: */
  70: function searchKeyword() {
  71:     var query = String(document.getElementById('query').value);
  72:     PGC.searchPoint(query, searchKeyword_callback);
  73: }
  74: 
  75: /**
  76:  * 住所・ランドマーク検索時のコールバック関数
  77:  * @param   Array  items[] {latitude:緯度, longitude:経度, address:住所}
  78:  * @param   Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
  79:  *                    hits:ヒット件数,webapi:WebAPIのURL}
  80:  * @return  なし
  81: */
  82: function searchKeyword_callback(items, pp) {
  83:     if (PGC.pp.error == false) {
  84:         $('#latitude').val(items[0].latitude);
  85:         $('#longitude').val(items[0].longitude);
  86:         makeTable();        //緯度・経度一覧
  87:     }
  88:     $('#errmsg').html(PGC.pp.errmsg);
  89: }
  90: 
  91: /**
  92:  * 緯度・経度一覧を作成する
  93:  * @param   なし
  94:  * @return  なし
  95: */
  96: function makeTable() {
  97:     n = PGC.items.length;
  98:     var html = `
  99: <table>
 100: <tr class="index">
 101: <th>No.</th>
 102: <th>住所</th>
 103: <th>緯度</th>
 104: <th>経度</th>
 105: </tr>
 106: `;
 107:     for (var key = 0key < nkey++) {
 108:         var html = html + `
 109: <tr>
 110: <td class="number">${key + 1}</td>
 111: <td class="text">${PGC.items[key].address}</td>
 112: <td class="number">${PGC.items[key].latitude}</td>
 113: <td class="number">${PGC.items[key].longitude}</td>
 114: </tr>
 115: `;
 116:     }
 117:     htlm = html + "</table>\n";
 118:     $('#results').html(html);
 119: }

ユーザー関数 searchKeyword が、前述の pahooGeoCode.searchPoint を使って、住所・ランドマークを緯度・経度に変換する。
コールバック関数 searchKeyword_callback は、APIから得られた緯度・経度をユーザー関数 makeTable によって一覧表表示する。

makeTable 関数では、PHPのヒアドキュメントに相当するテンプレート文字列JavaScript ES6(ES2015) から利用できるようになったので、これを活用している。

参考サイト

(この項おわり)
header