📖

Google Map Places API Nearby Searchの注意点 (前編)

2022/12/09に公開

はじめに

※この記事は Xbit Advent Calendar 2022 の10日目の記事です。

株式会社クロスビットは らくしふ を中心に、Rustによる自動シフトスケジューリング機能など魅力的な開発を行っている会社です。

https://note.com/xbit_recruit/n/na43181382616

この記事の要約

  1. Nearby Search API では実質 location(緯度/経度) と radius(半径) の指定が必須。
  2. pagetokenを渡す時はkeyword, location, radius 等は不要. 渡すと400が帰ってくる...
  3. pagetoken, next_page_token を使った複数APIコールを行っても、
    ある (キーワード, 緯度経度, radius) に対して 60件しか取れない。(1APICall で20件 * 3回分)

以上、これだけ押さえておけばOKです。

後の部分は読まなくても良いのでRRRでも観に行ってください。あれは最高のエンターテイメントです。

背景

この記事はクロスビットの事業内容とは全く関係ないのですが、私個人が友人から
「ある市町村内の全てのスポーツジム(※1)をリストアップすることはできないか?」
という相談を受けまして、Google Map Places API いくつか調べた際にわかったことをこの記事に記録します。

※1 実際にはスポーツジムだけではないのですが、今回そこはどうでも良い点なので何らかの事業所だと捉えてください。

Places API について

Google Map Places API の中にはたくさんのAPIが含まれていますが、今回はPlaces API全体の中の Place Search 系のAPIについて触れます。キーワード等の情報から複数のヒットした場所の情報を返してくれるAPIです。

とりあえずPlaces Search系というのは以下の3種類があります。

  • Find Place API
  • Nearby Search API
  • Text Search API

この記事ではNearby Search APIのみについて触れますが、Text Search APIについても同様に後述する注意点があります。

ドキュメントはこちら
https://developers.google.com/maps/documentation/places/web-service/search

利用ライブラリについて

Places APIを叩く際、自前のコード内でパラメータを組み立てて、直接http呼び出し行っても問題ありません。

ただ @googlemaps/google-maps-services-js とその型定義である @types/google.maps という npm パッケージをみつけたので今回は Node.js でこれらのライブラリを通してPlaces APIを呼び出してみようと思います。

各バージョンは次の通りです。

  • @googlemaps/google-maps-services-js -> 3.3.9
  • @types/google.maps -> 3.50.4

具体的なコードとAPI挙動

1. NearBySearch API呼び出しのサンプルコード

import { Client } from "@googlemaps/google-maps-services-js";
import { Language } from "@googlemaps/google-maps-services-js";

const client = new Client({});
const response = await client.placesNearby({ 
  params: {
    key: PLACES_API_APIKEY,
    language: Language.ja,
    keyword: "スポーツジム"
    location: {lat: 35.6217, lng: 139.7192 },
  },
});

if (response.status !== 200) {
  // エラー処理
}

// response.data.results.length は 最大20件です。
// response.data.next_page_token を placesNearby({ pagetoken: ... }) に渡すと21件目以降が取得できます 

console.table(response.data.results.forEach((r) => [r.name, r.vicinity]))

「はいはい、なるほどね、こういうかんじね… まずこれで最初の20件はとれるわけね..」

って思ったあなた、残念ながらこのコードはうごきません。

if (response.status !== 200) { ではハンドリングできない例外がとんできます。

エラーをキャッチするとこんな例外オブジェクトがとんできます。
(内部的にaxiosを使っているのが露出してますね…)

いや… ERR_BAD_REQUESTじゃ何もわからねえ。
まったく勘弁してくれよ Places API はAPI呼び出しごとに金払うんだぜ…
(とか言わない方が良いんですよね)

@googlemaps/google-maps-services-js, @types/google.maps の型

原因を探っていくために、引数に誤りがないか確認しました。

さきほどの placesNearby の引数はこんな型が定義されています。

型定義上は必須なパラメータ は location だけです。

APIのキーがコピペミスで間違った値になっていた、なんてこともありませんでした。

ではなぜ、AxiosError が飛んでくるのでしょう….

原因: radius の指定が必要でした。

radiusパラメータは、params.location で指定した緯度,経度を中心に半径何メートルまでを検索対象とするかを指定するものです。

以下のコードで結果が帰ってきました。エニタイムフィットネスなど合計20件がヒットしました.

やったー。

const response = await client.placesNearby({ 
  params: {
    key: PLACES_API_APIKEY,
    language: Language.ja,
    keyword: "スポーツジム"
    location: {lat: 35.6217, lng: 139.7192 },
    radius: 1000, // 半径1km を指定してみました。
  },
});

APIドキュメントを見ると、radius について以下のように書かれてありました。
Note that radius must not be included if rankby=distance is specified.

https://developers.google.com/maps/documentation/places/web-service/search-nearby?lh=en#radius

パラメータで rankby=distance を行う場合は 不要なので、Typescriptの型定義では

raidus: number ではなく radius?: number | undefined になっているんでしょうね。

良い型の付け型としては用途ごとの Interface を Union で書き連ねるのがよいと思います。

こんな雰囲気です。

type TheParams = LocationWithRadiusParams | RankByDistanceParams

interface LocationWithRadiusParams {
  location: LatLng;
  radius: number;
  ...
} & RequsetParams

interface RankByDistanceParams {
  location: LatLng;
  ...
} & RequsetParams

複数のパラメータ指定が可能なインターフェースを作る場合は、呼び出し側のコードを書く人が、実行時ではなくTypescriptのビルド時にパラメータ指定の誤りに気づける表現がよいとおもいます。
(自戒)

残りの注意点

さて、残りの2つPlaces APIの注意点を書きたいのですが、もうみなさんお腹がいっぱいですよね。

アドベントカレンダーの期間はまだあるので、残りはまた別の記事にしたいと思います。

さいごに

株式会社クロスビットでは、デスクレスワーカーのためのHR管理プラットフォームを開発しています。
一緒に開発を行ってくれる各ポジションのエンジニアを募集中です。

https://x-bit.co.jp/recruit/
https://herp.careers/v1/xbit/requisition-groups/2e95da36-0045-469f-b92e-c648f701ed5a
https://note.com/xbit_recruit

Discussion