【AppSheet】(その2)周辺検索アプリを作ってみた(コンビニ、レストラン編)
(何故これを書いたか?)
前編の続きです。AppSheet+Google Map系API(2個)を使って周辺検索アプリを作る話を書いています。アプリの内容は、例えば自宅や職場、あるいは現在地から半径5Km以内にあるコンビやレストランを距離順に表示するといったものです。前編はAPIの説明が長くなってしまい、1話完結にならなかったので3回に分けて解説します。
AppSheet使ってアプリを作ったのはこれが初めてでしたが、さすがGoogleが買収したアプリだけあってGoogleMapなどとの連携がシームレスに出来て驚きました。地図系アプリを作るならAppSheetにかなうものは無いんじゃないかと感じました。
(主な解説内容)
1話:検索アプリの概要
2話(今回):スプレッドシート(GAS)からGoogle Map系APIを呼び出す
3話:AppSheetでの設定内容
1.スプレッドシートからAPIを呼び出す
前編でも書きましたがAppSheetでAPIを使うにはEnterpeise plus以上の契約をしていないとダメで、私の場合はそれに該当しないのでAPIは使えないという状況でした。
Appsheetはデータベースをスプレッドシートを使っているのでスプレッドシートにGASでスクリプトを書いてAPIを呼び出すことにしました。
(今回使ったAPI)
今回アプリを作るのに以下2つのAPIを使いました
①Google Geocoding API
②Google Places API nearbySearch(NEW版)
2.Google Geocoding APIのリクエスト
Geocoding(ジオコーディング)とは日本語で入力した住所を緯度経度の値に変換することを言います。以下のリクエストをfetchで送ってやれば緯度(latitude)、経度(longitude)別々のデータがJSONで送られてきます。
「https://maps.googleapis.com/maps/api/geocode/json?address=日本語住所 &key=YOUR_API_KEY」
このURLを以下のようにfetchでGoogleのAPIに送ってやる(GASでの記述)
const response=UrlFetchApp.fetch(url);
(ポイント)
戻り値はJSONになっています。緯度(lat)、経度(lng)のデータが入っているのですが、入れ子になっておりどうやって取り出すのか悪戦苦闘しました。返ってきたJsonのデータをJSON.parseで配列にしてキーとバリューの関係で取り出すことが出来ました。
const response=UrlFetchApp.fetch(url);
const jsonData=JSON.parse(response.getContentText());
lat=jsonData['results'][0]['geometry']['location']['lat'];//緯度 lng=jsonData['results'][0]['geometry']['location']['lng'];//経度
(緯度と経度の取り出し方)
返ってきたjsonDataはJSON.parseによって以下のような配列になっています
'results':[{・・・・・・ },{'status':'ok'}]
この中で{・・・・・・}の部分に緯度、経度のデータが別々に収納されています。以下の手順でデータを取り出しました。
①{・・・・・・}部分をまず取り出す
配列[ ]の1番目のデータ(オブジェクト)なのでインデックス[0]を付けて値を取得します。
['results'][0]
②latとlngはgeometryとlocationの下にある(オブジェクトになっている)
'geometry':{'location':{'lat':36.6234,'lng':139.87256}}
つまり、以下のようにすれば値を取り出すことが出来ます
latの値→ ['geometry']['location']['lat'];
lngの値→ ['geometry']['location']['lng';]
③返り値('results')の中からlat(緯度)とlng(経度)を取り出す
lat=['results'][0] ['geometry']['location']['lat'];
lng=['results'][0] ['geometry']['location']['lng';]
私の過去の経験からするとNoCodeのAdaloやClick等だと入れ子になっているJSONのデータが取り出せないんですよ。配列のインデックスを使ってデータを取り出せるというのはスプレッドシート(GAS)の優位な点だと思います。
3.Places API nearbySearch(NEW)
Places API nearbySearch (NEW)のリクエストの書き方
以前のnearbySearch(旧版)では1行のURLで書けましたが、新版(NEW)ではパラメータが豊富に設定されていて下記例のようにcURLでリクエストするようになっています。これをGAS(Google Apps Script)で書くやり方が当初分からず相当迷いました。
上記cURLの内容をよく見ると以下の部分に分かれていることが分かります。
・リクエストのURL
・ヘッダー部分(-H)
・データ部分(-d)
・オプション部分(-X等)
GASで書く場合、変数を定義するときのように以下のように記述します。
const 変数=( )
変数にヘッダー、データ、オプションなどを設定していきます。
※POSTでデータ飛ばすのですが、このPOSTはoptionの中に記述し、optionはfetchの引数として使います。この設定がなかなか分からなかった。
const response=UrlFetchApp.fetch(url,options);
const url="https://places.googleapis.com/v1/places:searchNearby"
const headers={"Content-Type":"application/json",
"X-Goog-Api-Key":"Your-API-Key",
"X-Goog-FieldMask":"places.displayName,places.formattedAddress,places.websiteUri",
}
const data={"includedTypes": ["restaurant"],//
"maxResultCount": 10,
"languageCode":"ja",
"rankPreference":"DISTANCE",
"locationRestriction": {
"circle": {
"center": {
"latitude": 37.7937,
"longitude":-122.3965},
"radius": 500
}
}
}
const options = {
"method": "post",
"payload": JSON.stringify(data),
"headers": headers
}
const response=UrlFetchApp.fetch(url,options)
const jsonData2=JSON.parse(response.getContentText());
(距離順にリストを並べる設定)
nearbySearchでは検索された結果を「距離順(近い順番)」、「評価順(評価が高い順)」のいずれかで並べることが出来ます。指定しなければ評価順になります。
この設定はdataの部分にあるrankPreference(並び順)で行います。
距離準の場合:"rankPreference":"DISTANCE"
評価順の場合:"rankPreference":"POPULARITY"
※上のGASのスクリプトの中ではdataの中に距離順の指定をしています
"rankPreference":"DISTANCE"
(ポイント)
スプレッドシート(GAS)からAPIのURLにアクセスするにはfetchメソッドを使って以下のように記述します。
const response=UrlFetchApp.fetch(url,options)
Logger.logでresponseの内容を見てみるとJSONになっていますが、今度はJSON.parseでJSONの配列に格納します。ここもハマったポイントでした。JSONの戻り値が複雑な入れ子になっている場合、データが配列になっていないとデータがうまく取り出せないのです。
(JSONのレスポンスから各データを取り出す)
const response=UrlFetchApp.fetch(url,options)
const jsonData2=JSON.parse(response.getContentText());
返ってくるJSON(ここではjsonData2)の形は以下のようになっている
"places":[{・・・・・},{"status":"ok"}]
{・・・・・}の中に検索結果の店舗名、住所、webサイトなどが書かれている。配列の1番目にデータがあるので以下のようにしてデータを取り出す。(配列の1番目なのでインデックス[0]をつけて値を取り出しています)
施設名 :jsonData2["places"][0]["displayName"]["text"]
住所 :jsonData2["places"][0]["formattedAddress"].slice(13)
Webサイト:jsonData2["places"][0]["websiteUri"]
(注)住所の最後に.slice(13)とある理由
戻りの住所が「日本、〒321-0818 栃木県宇都宮市・・・・」のような表記になっているので県名から始まるように先頭のいらない部分をsliceで切り取っています。slice(13)は13文字目以降の文字列を取得するという意味です。
4.その他の苦戦部分(現在地の緯度・経度)
①現在地の緯度経度のデータの取り出し
今回のアプリでは日本語で住所を入力して、そこを基準場所として近所のコンビニやホテルなどを検索する機能を持っていますが、現在地を基準にして検索も出来るようにしました。しかし当初はどうやれば現在地の住所(緯度経度)が取得できるのか分かりませんでしたが、さんざん試行錯誤した末に以下のようにすればいいことが分かりました。
(現在地の緯度経度情報の取得方法)
Appsheetのデータ入力画面でデータ型(Type)を緯度経度(latLong)に指定してやれば入力欄にマップピンのアイコンが表示され、そこをクリックすれば緯度経度が入力されるのです。さすがGoogle!やることがにくいね。
(AppSheet入力画面)
②現在地の緯度経度を別々のデータに分ける方法
Appsheetの標準機能を使うと以下のような形で現在地の緯度経度情報
が得られることが分かりました。
入手できる緯度経度データ(例): 36.67231,139.806784
ただ、この情報をGoogle Places API nearbySearchで使うには緯度と経度は別々に分けなければならず、ここでも散々つまづいてしまいました。
緯度と経度のデータは 「 , 」 で区切られているので関数を使えば簡単に分けることが出来ます。しかしいくら分離してもエラーがでてしまい原因が分かりませんでした。
(誤ったGASでの記述)
下記ではスプレッドシートのD列の最終行に緯度経度データが入力されるという前提でスクリプトを書いています。
const latLng=sheet.getRange(lastRow,4).getValue();
const lat=latLng.split(",")[0];
const lng=latLng.split(",")[1];
splitメソッドは「,」で文字列を分割し、配列に収納するという機能を持っています。つまり36.6,139.9というデータは「,」で分離され["36.6","139.9"]という配列になるということです。緯度の36.6は配列の[0]、経度の139.9は配列の[1]に収納されるということです。
(正しいGASの記述)
const lat=Number(latLng.split(",")[0]);
const lng=Number(latLng.split(",")[1]);
AppSheetが作り出す緯度経度情報は文字列なんですね。それを分割して数字にしようと思ってもやはり文字列は文字列なんです。見かけは数字なんですが、属性としては文字列で、それが証拠に足し算すると数字の足し算にならず、文字列がくっついた状態になってしまいました。
見かけ数字の文字列を本当の数字に変えるにはNumber(text)で変換しました。これでエラーが消えて現在地からの検索もできるようになりました。
(GASの記述でハマった部分)→下記3か所(※自分の備忘録として)
1.Geocoding API
・住所は日本語OKという事が分からず、エンコードの方法を探していたが、それが現在では不要だった。(以前は日本語は受け付けられなかった)
・レスポンスで返ってきたJSONの中から緯度と経度を別々に取得する方法が分からなかった。
2.NarbySearch
リクエストをcURLで送信する際GASではどう記述するのか分からなかった。
3.現在地の緯度経度情報
AppSheetの標準機能の現在地の緯度・経度データ(文字列)を各々数字にばらすやり方が分からなかった。
自分の備忘録として記録しておきます。次回(3回目)はAppsheetの設定についてご説明します。