JavaScriptで「ぐるなび」を使って喫茶店を探す

(1/1)
JavaScript ES6 がIE以外のブラウザで利用できるようになった。このバージョンではクラス宣言もできる。
そこで今回は、「PHPで『ぐるなび』を使って喫茶店を探す」で紹介したPHPサーバサイド・プラグラムを、JavaScriptを使ってクライアント・サイドに移植してみる。PHP環境が無くてもブラウザさえあれば、Googleマップ上で喫茶店を検索できるようになる。

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

JavaScriptで「ぐるなび」を使って喫茶店を探す

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

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

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

JavaScriptで「ぐるなび」を使って喫茶店を探す
プログラムの流れは、冒頭で jQuery とクラス pahooGeoCode を読み込み、各種初期化を行う。
続いて、3つのボタン押下によって、各々 exec, map, clear のアクションを発生する。
execは、「JavaScriptでWebAPIを利用して住所から緯度・経度を求める」と同じで、キーワードから緯度・経度を求める。
mapは、今回のメインプログラムで、詳細は後述する。
clearは、初期化してからmapと同じ処理を行う。

解説:クラスファイル

住所やランドマークから緯度・経度を求める処理は、「JavaScriptでWebAPIを利用して住所から緯度・経度を求める」で作成したクラス pahooGeoCode を利用する。
クラスファイル "pahooGeoCode.js" の配置、各種WebAPI用のアプリケーションIDの設定方法については、「準備、クラスについて」を参照してほしい。

解説:Googleマップ描画

0280: /**
0281:  * Googleマップを描く
0282:  * @param String  id        マップID
0283:  * @param Float   latitude  中心座標:緯度(世界測地系)
0284:  * @param Float   longitude中心座標:経度(世界測地系)
0285:  * @param String  type      マップタイプ:HYBRID/ROADMAP/SATELLITE/TERRAIN
0286:  * @param Integer zoom      拡大率
0287:  * @param String  call      イベント発生時にコールする関数(省略可)
0288:  * @param Array   items[]   地点情報(省略可能)
0289:  *                  'title'       タイトル
0290:  *                  'description' 情報ウィンドウに表示する内容
0291:  *                  'latitude'    緯度
0292:  *                  'longitude'   経度
0293:  *                  'icon'        アイコンURL
0294:  * @return String Googleマップのコード
0295: */
0296: drawGMap(idlatitudelongitudetypezoomitems=null) {
0297:     var map = new google.maps.Map(document.getElementById(id), {
0298:         centernew google.maps.LatLng(parseFloat(latitude), parseFloat(longitude)),
0299:         zoomparseInt(zoom),
0300:         mapTypeIdtype,
0301:         mapTypeControltrue,
0302:         scaleControltrue
0303:     });
0304:     map.addListener('dragend', getPointData);
0305:     map.addListener('zoom_changed', getPointData);
0306:     map.addListener('maptypeid_changed', getPointData);
0307: 
0308:     //イベント発生時の地図情報を取得・格納
0309:     function getPointData() {
0310:         var point = map.getCenter();
0311:         //経度
0312:         if (document.getElementById('longitude') != null) {
0313:             document.getElementById('longitude').value = point.lng();
0314:         }
0315:         //緯度
0316:         if (document.getElementById('latitude') != null) {
0317:             document.getElementById('latitude').value = point.lat();
0318:         }
0319:         //ズーム
0320:         if (document.getElementById('zoom') != null) {
0321:             document.getElementById('zoom').value = map.getZoom();
0322:         }
0323:         //地図タイプ
0324:         if (document.getElementById('type') != null) {
0325:             var type_g = map.getMapTypeId();
0326:             const types = {'roadmap':'地図', 'satellite':'航空写真', 'hybrid':'ハイブリッド', 'terrain':'地形図'};
0327:             for (var key in types) {
0328:                 if (key == type_g) {
0329:                     document.getElementById('type').value = key;
0330:                     break;
0331:                 }
0332:             }
0333:         }
0334:     }
0335: 
0336:     if (items != null) {
0337:         var n = items.length;
0338:         items.forEach(function(itemi) {
0339:             if (i <= 26) {           //'Z'を超えたらスキップ
0340:                 //マーカー
0341:                 var mark = String.fromCodePoint('A'.charCodeAt(0) + i);
0342:                 var icon_url = (typeof(item.icon) == 'undefined') ? 'https://www.google.com/mapfiles/marker' + mark + '.png' : item.icon;
0343:                 var marker = new google.maps.Marker({
0344:                     positionnew google.maps.LatLng(item.latitudeitem.longitude),
0345:                     iconicon_url,
0346:                     mapmap,
0347:                     titleitem.title,
0348:                     zIndex: 100
0349:                 });
0350:                 var infowindow = new google.maps.InfoWindow({
0351:                     contentitem.description,
0352:                     maxWidth: 200
0353:                 });
0354:                 google.maps.event.addListener(marker, 'click', function() {
0355:                     infowindow.open(mapmarker);
0356:                 });
0357:             }
0358:         });
0359:     }
0360: }

クラス pahooGeoCode に、Googleマップを描画するメソッド drawGMap を追加した。これもPHPプログラムを、ほぼそのまま移植している。

PHPプログラムではマーカーを静的に発生させていたが、JavaScript版ではメソッドの後半で、引数 items にforEachループをかけ、動的にマーカーを発生させている。forループを使って実装すると、new でマーカー・オブジェクトを追加生成する処理がうまくいかないので、あえてforEachループを使っている。

「ぐるなびWebサービス:レストラン検索API」による店舗探索

ぐるなびWebサービス:レストラン検索API」は、入力パラメータ(IN)は GET 渡しで、出力結果(OUT)は JSON で戻すという形である。今回使う入力パラメータと出力結果のデータ構造の一部を以下に記す。
WebAPI
URL
https://api.gnavi.co.jp/RestSearchAPI/v3/

入力パラメータ
フィールド名 要否 内  容
keyid 必須 ぐるなびより提供されたアクセスキー
input_coordinates_mode 任意 入力パラメータに含まれる緯度/経度の測地系を指定
1:日本測地系
2:世界測地系(デフォルト)
latitude 任意 検索地点の緯度(小数表記)
longitude 任意 検索地点の経度(小数表記)
coordinates_mode 任意 レスポンスに含まれる緯度/経度の測地系を指定
1:日本測地系
2:世界測地系(デフォルト)
range 任意 緯度/経度からの検索範囲(半径)
1:300m、2:500m(デフォルト)、3:1000m、4:2000m、5:3000m
freeword 任意 検索ワードをUTF-8でURLエンコードすること「,」区切りで複数ワードが検索可能(10個まで)
出力パラメータ (JSON)
フィールド名 内  容
@attributes api_versionAPIのバージョン
total_hit_count該当件数
hit_per_page表示件数
page_offset表示ページ
total_hit_count表示ページ
rest[] id店舗ID
update_date情報更新日時
name店舗名
name_kana店舗名称(カタカナ)
latitude緯度(小数表記)
longitude経度(小数表記)
categoryフリーワードカテゴリー
urlPCサイトURL
url_mobile携帯サイトURL
address住所
tel電話番号
opentime営業時間
holiday休業日

アプリIDの入手

0031:     //ぐるなびWebサービス アクセスキー:https://api.gnavi.co.jp/api/ で発行
0032:     GNAVI_ACCESSKEY = '*************************************';

ぐるなびWebサービス」を利用するには、利用申請を行い、アカウントを発行してもらい、アクセスキーを取得する必要がある。アカウントは無料である。

サンプル・プログラムをダウンロードしたら、定数 GNAVI_ACCESSKEY に自分のアクセスキーを記述する。

解説:ぐるなびWebサービス

0179: /**
0180:  * ぐるなびWebサービスのURLを取得する
0181:  * @param Float  latitude  緯度(世界測地系)
0182:  * @param Float  longitude 経度(世界測地系)
0183:  * @param Float  distance  範囲(メートル)
0184:  * @param String freeword  フリーワード検索(カンマ区切り)
0185:  * @return String URL 電源情報APIのURL
0186: */
0187: function getURL_RestSearchAPI(latitudelongitudedistancefreeword) {
0188:     var range_tbl = [0, 300, 500, 1000, 2000, 3000];
0189: 
0190:     var keyid = GNAVI_ACCESSKEY;
0191:     range = range_tbl.length;
0192:     for (var key = 1; key < rangekey++) {
0193:         if (distance <= range_tbl[key]) {
0194:             range = key;
0195:             break;
0196:         }
0197:     }
0198:     freeword = encodeURI(freeword);
0199: 
0200:     url = 'https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=' + keyid + '&input_coordinates_mode=2&coordinates_mode=2&latitude=' + latitude + '&longitude=' + longitude + '&range=' + range + '&freeword=' + freeword;
0201:     return url;
0202: }
0203: 
0204: /**
0205:  * レストラン検索APIを利用して指定座標の近くにある喫茶店を検索
0206:  * @param Array  items[]   情報を格納する配列
0207:  * @param Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
0208:  *                    hits:ヒット件数,webapi:WebAPIのURL}
0209:  * @returnなし
0210: */
0211: function searchCafe(itemspp) {
0212:     var latitude  = parseFloat($('#latitude').val());
0213:     var longitude = parseFloat($('#longitude').val());
0214:     var distance  = parseInt($('#distance').val());
0215:     items = [];
0216:     var url = getURL_RestSearchAPI(latitudelongitudedistance, '喫茶店');
0217:     var n = 0;
0218: 
0219:     //API呼び出し
0220:     var req = new XMLHttpRequest();
0221:     req.onreadystatechange = function() {
0222:         searchCafe_callback(reqitemspp);
0223:     };
0224:     req.onreadystatechange = function() {
0225:         if (req.readyState == 4) {
0226:             if (req.status == 200) {
0227:                 searchCafe_callback(req.responseTextitemspp);
0228:                 pp.error  = false;
0229:                 pp.errmsg = '';
0230:             } else {
0231:                 searchCafe_callback(req.responseTextitemspp);
0232:             }
0233:         }
0234:     };
0235:     try {
0236:         req.open('GET', urltrue);
0237:         req.send(null);
0238:     } catch(e) {
0239:         pp.error  = true;
0240:         pp.errmsg = 'エラー:レストラン検索APIが呼び出せない.';
0241:         pp.hits   = 0;
0242:         searchCafe_callback(nullitemspp);
0243:     }
0244: }
0245: 
0246: /**
0247:  * レストラン検索APIのコールバック関数
0248:  * @param String results APIのリターン値
0249:  * @param Array  items[]   情報を格納する配列
0250:  * @param Object pp{error:エラーフラグ,errmsg:エラーメッセージ,
0251:  *                    hits:ヒット件数,webapi:WebAPIのURL}
0252:  * @returnなし
0253: */
0254: function searchCafe_callback(resultsitemspp) {
0255:     if (results != null) {
0256:         var json = JSON.parse(results);
0257:         var n = 1;
0258:         //情報取得
0259:         if (json.total_hit_count > 0) {
0260:             json.rest.forEach(function(val) {
0261:                 var obj = new Object();
0262:                 obj.id        = String.fromCodePoint('A'.charCodeAt(0) + n - 1);
0263:                 obj.title     = val.name;
0264:                 obj.url       = val.url;
0265:                 obj.category  = val.category;
0266:                 obj.phone     = val.tel;
0267:                 obj.opentime  = val.opentime.replace(/\n/ui, '<br />');
0268:                 obj.holiday   = val.holiday.replace(/\n/ui, '<br />');
0269:                 obj.address   = val.address.replace(/\n/ui, '<br />');
0270:                 obj.address   = obj.address.replace(/〒[0-9\-]+ /ui, '');
0271:                 obj.latitude  = parseFloat(val.latitude);
0272:                 obj.longitude = parseFloat(val.longitude);
0273:                 obj.description   = `
0274: <a href="${obj.url}target="_blank">${obj.title}</a><br />電話:${obj.phone}<br />住所:${obj.address}<br />営業時間:${obj.opentime}<br />定休日:${obj.holiday}
0275: `;
0276:                 items.push(obj);
0277:                 n++;
0278:             });
0279:             pp.error  = false;
0280:             pp.errmsg = '';
0281:             pp.hits   = n;
0282: 
0283:         //エラー
0284:         } else if (typeof json.error != 'undefined') {
0285:             pp.error  = true;
0286:             pp.errmsg = 'エラー:' + json.error[0].message;
0287:             pp.hits   = 0;
0288:         }
0289:     }
0290:     //Googleマップ描画
0291:     var zoom = distance2zoom($('#distance').val());
0292:     PGC.drawGMap(GMAPID$('#latitude').val(), $('#longitude').val(), $('#type').val(), zoomitems);
0293: 
0294:     //一覧表作成
0295:     $('#results').html(makeTable(items));
0296: 
0297:     //エラーメッセージ
0298:     $('#errmsg').html(PGC.pp.errmsg);
0299: }
0300: 
0301: /**
0302:  * レストラン検索APIを利用して指定座標の近くにある喫茶店を検索し
0303:  * Googleマップにマッピングし、一覧表を表示する

ぐるなびWebサービス:レストラン検索API」を XMLHttpRequest によって呼び出し、店舗情報をオブジェクト配列 items に格納するのが、ユーザー関数 searchCafe である。
JavaScriptでWebAPIを利用して住所から緯度・経度を求める」と同様、非同期処理になるため、APIから戻ってくるJSONの処理はコールバック関数 searchCafe_callback で行う。

関数 searchCafe_callback では、WebAPIの応答を JSON.parse を使って解釈し、オブジェクト配列 items に格納してゆく。
Googleマップに描画する際に必要になるので、要素 description には、店舗名、電話番号、住所、営業時間、定休日とリンク先URLをハイパーリンクする。

最後に、Googleマップへの描画、一覧表の作成、エラーメッセージの表示を行い、コールバックを完了する。

参考サイト

(この項おわり)
header