Google Maps Static APIを使ってツーリングで巡ったルートを地図表示する
はじめに
前回、StravaのAPIを使ってサイクリングの情報を持ってくる方法を記事にしました。
Stravaにはどこを走ったかも記録されていて、位置情報が以下のように地図に表示されています。
ちなみにこれは去年今年に北海道で年越しツーリングして大晦日に吹雪に遭ったときのアクティビティです。
そのアクティビティをAPIで取得すると位置情報は以下のフォーマットで取得されます。
なんのこっちゃって感じですね。
Strava APIから取得した位置情報もこうなっては人間が読めないので地図画像として出力する必要があります。
今回はGoogle Map Static APIを使ってこの呪文みたいな文字列からStravaに表示されるような地図を生成します。
Google Maps Static APIとは
Google Mapの一部を画像として切り出して返してくれるAPIです。
あらゆる情報をパラメータとして指定することで追加の情報を付加した地図も返せます。
今回は位置情報を付加した地図を返します。
APIキー取得まで
プロジェクトの作成
まずは前提として、Googleアカウントが必要です。持っていない場合はアカウントを作成してください。
Googleにログインした後は、プロジェクトを作成します。 Google Cloud Platformにアクセスします。
右上の「プロジェクトを作成する」から新規のプロジェクトを作成します。
自分がわかるような名前を付けます。
作成後は作成したプロジェクトの画面に遷移します。
支払情報の登録
APIの使用は従量課金制です。APIを使用する前にプロジェクトに支払い方法を登録する必要があります。
Google Maps Static APIの料金
しかし、
なお、すべての Google Maps Platform ユーザーは、課金が有効になっているアカウントで、毎月 200 ドルのクレジットを受け取ります。
とあるので、2022年6月16日現在はGoogle Maps Static APIのみの利用に限れば、月に10000回までは無料でAPIを叩けます。
それ以上使う場合はプロジェクトに登録された支払い方法によって、従量課金が発生します。
プロジェクトに支払情報を登録する
Google Cloud Platformトップに戻ってサイドバーの「お支払い」を押す。
「請求先アカウントをリンク」を押す。
「請求先アカウントを作成する」を押す。
ここから請求先を登録していきます。まずは必要事項を入力して利用規約を読んで同意します。
プロジェクトのニーズは自分の場合は「個人的なプロジェクト」にしました。
連絡先を確認します。SMSを受信できる電話番号を入力します。
コードを送信すると、入力した電話番号宛にSMSが届きます。
SMSの中に記載されているコードを入力します。
次にお支払い情報の入力です。有効なクレジットカードの情報と住所を入力します。
これで登録は完了です。再度、お支払いのページを見ると、紐付いている請求先アカウントの情報が表示されます。
APIを有効化する
使用するAPIを選びます。 左のサイドバーから「APIとサービス」 -> 「有効なAPIとサービス」を選びます。
「APIとサービス」画面が開くので上の「APIとサービスの有効化」を押します。
Google Maps Static APIを探します。 staticなどで検索するとヒットすると思います。
該当するAPIを選択します。
「有効にする」を押すと、有効になります。
そのまま有効なAPIが表示される画面に遷移します。
次にAPIキーを発行します。 サイドバーから「認証情報」に遷移して上の「認証情報を作成」からAPIキーを選択します。
すると、APIキーが発行されて表示されます。
Google Maps Static APIを使ってみる
APIキーを利用して画像を表示させてみます。 APIのエンドポイントは以下のようなフォーマットです。
https://maps.googleapis.com/maps/api/staticmap?center=[LATITUDE],[LONGTITUDE]&zoom=[ZOOM_LEVEL]&size=[WIDTH]x[HEIGHT]&key=[API_KEY]
クエリパラメータのcenterにある[LATITUDE]
,[LONGTITUDE]
は表示する地図の中心の緯度経度を入力します。
zoomはズームレベルといって地図の縮尺です。 詳細は
の「正方形タイルによる地図表現」セクションに載せています。
sizeの[WIDTH]
x[HEIGHT]
は表示する地図画像のサイズです。
keyの[API_KEY]
は先程発行したAPIキーを入力します。
例えば、小田原駅の地図を表示してみます。 Google Mapで小田原駅を真ん中に寄せたときのURLが
https://www.google.co.jp/maps/@35.2562828,139.1554468,17z?hl=ja
です。URL内に緯度経度とズームレベルがあるのでこれを使います。
画像サイズは600x600にしてみます。すると叩くAPIは
https://maps.googleapis.com/maps/api/staticmap?center=35.2564493,139.1532045&zoom=17&size=600x600&key=[API_KEY]
です。APIキーは伏せているので各自のキーに置き換えてください。
GETリクエストなのでブラウザにURL貼って遷移すると、地図画像が表示されました。
Google Mapで見た地図とまんま一緒です。
これでAPIが使えることを確認できました。
署名の導入
今のままでもAPIは使えますが、GoogleはAPIを叩くときにリクエストを署名することを推奨しています。
We strongly recommend that you use both an API key and digital signature, regardless of your usage.
リクエストにはAPIキーが含まれています。このAPIキーが何かしらの原因で抜き取られた場合は第三者がAPIを叩けてしまいます。 本人だけが持っている情報でリクエストを署名することで第三者が抜き取ったAPIキーだけではAPI叩けないようにして安全性を高めます。
こちらにGoogle Maps Static APIを使うときに署名する理由などが書いてあります。
ちなみに前回説明した
Strava APIはOAuth2.0による認証です。
仕組みなど定義はこちらがわかりやすかったです。
この中の「認可コードフロー」と「リフレッシュトークンフロー」をStrava APIは採用しています。
GoogleのAPIはGoogle DriveのAPIなど個人のデータにアクセスするAPIにはOAuth2.0を採用して、 Google Maps Static APIのような取得する地図データは個人情報にあたらないのでAPIキーによる認証を採用しているようです。
署名を試す
URLやパラメータの署名のやり方です。まず、Google Maps Platformに行くと、先程発行したAPIキーともう一つ、URL 署名シークレットというものがあります。
これが署名には必要です。発行されていない場合はボタンを押して発行してください。
発行後、「URL に署名」の欄から試しにURLを署名することができます。先程の小田原駅を表示するURLを署名してみます。
https://maps.googleapis.com/maps/api/staticmap?center=35.2564493,139.1532045&zoom=17&size=600x600&key=[API_KEY]
APIキーも含めてフォームに貼り付けます。
すると、署名済みのURLが返ってきます。
署名前のURLとの違いは最後のクエリパラメータにsignatureが追加されていることです。
https://maps.googleapis.com/maps/api/staticmap?...&key=[API_KEY]&signature=[SIGNATURE]
このsignatureが署名済みであることを示しています。
このsignatureが付加された状態でAPIを叩くときはURL署名シークレットとリクエストURLで署名をしてみて、[SIGNATURE]
と一致するかどうかを検証します。
一致した場合正しい署名(=第三者によるリクエストではない)なので通常通りレスポンスとして画像が返ってきます。一致しない場合はエラーを返します。
ちなみに署名に使われる関数は一方向関数で[SIGNATURE]からURL署名シークレットを算出することは難しいです。
署名済みのURLをブラウザに入力してリクエストを送ると、先程と全く同じ画像が出力されるはずです。
署名を実装する
署名を試すことはできましたが、実際APIを使うのはコード中なので、未署名のURLから署名済みのURLを作る実装が必要です。
署名は
- 署名シークレットをURLセーフのbase64*1から通常のbase64へ変換する
- 通常のbase64になった署名シークレットをデコードする
- デコードした署名シークレットをキーとしてリクエストのURLをHMAC-SHA1*2でハッシュ化する
- ハッシュをURLセーフのbase64に変換する
- 署名済みとしてsignatureパラメータに↑のbase64のハッシュを渡す
といった流れで行われます。
公式が様々な言語のサンプルコードを出しているのでこれを踏まえて実装していきます。
現在、開発しているアプリがTypescript + NodeJSなのでNodeJSのサンプルコードからdeprecatedなも部分を改変してTypescriptで書きました。
import crypto from "crypto"; import url from "url"; ... function removeWebSafe(safeEncodedString: string) { return safeEncodedString.replace(/-/g, "+").replace(/_/g, "/"); } function makeWebSafe(encodedString: string) { return encodedString.replace(/\+/g, "-").replace(/\//g, "_"); } function decodeBase64Hash(code: string) { return Buffer.from(code, "base64"); } function encodeBase64Hash(key: Buffer, data: string) { return crypto.createHmac("sha1", key).update(data).digest("base64"); } function async sign(apiPath: string, signatureSecret: string) { const uri = new URL(apiPath); const safeSecret = this.decodeBase64Hash( this.removeWebSafe(signatureSecret) ); const hashedSignature = this.makeWebSafe( this.encodeBase64Hash(safeSecret, uri.pathname + uri.search) ); return url.format(uri) + "&signature=" + hashedSignature; }
API呼び出しの制限
署名によるリクエストを設定したので未署名のリクエストはできないよう設定します。
Google Maps Platformから「割り当て」を選びます。
未署名のURLによるリクエストの設定を展開して、各API呼び出し回数の上限をすべて0にします。
これで未署名のURLでリクエストを送ると
このような画像が返ってきました。制限できているっぽいです。
位置情報を付与した地図を取得する
やっとメインコンテンツです。
地図自体の表示はできているので、次はその地図に位置情報によるルート表示をしてみます。
これまでの地図表示のAPIにpathというパラメータを足すだけです。
pathのフォーマットは以下のようになっています。
path=weight:[WEIGHT]|color:[COLOR]|[LAT1],[LON1]|[LAT2],[LON2]|...
[WEIGHT]
は表示するルートの線の太さです。
[COLOR]
は線の色です。カラーコードや名称で指定できます。
[LAT1]
,[LON1]
は位置情報です。|
で繋げて複数の緯度経度を指定するとその位置を通る線を引っ張ります。
オプションは他にもあります。他のオプションについては公式Docを参照してください。
他のオプションも|
で繋いでいきます。
ただし、|
はURLにおいて無効な文字なのでURLエンコードする必要があります。URLエンコードすると、|
-> %7C
に変わります。
これを元に小田原駅(35.2564493,139.1532045)から国府津駅(35.2811428,139.2140066)までの線を引く地図は以下のURLで取得できます。
https://maps.googleapis.com/maps/api/staticmap?size=600x600&path=weight:5%7Ccolor:blue%7C35.2564493,139.1532045%7C35.2811428,139.2140066&key=[API_KEY]&signature=[SIGNATURE]
APIを叩くと以下のような画像を取得できます。
これを応用してStrava APIから取得した位置情報を地図に付加します。
冒頭で述べたとおり、Strava APIから返ってくる位置情報は緯度経度で表示されていません。
この文字列は緯度経度をPolyline Encoding と呼ばれるエンコーディングを行ったものです。
ちなみにGoogle Maps Static APIはこのPolyline Encoding による位置情報もそのままリクエストに渡せることができます。
pathパラメータにenc
を足し、そこにPolyline Encoding による位置情報を与えます。
Polyline EncodingにはURLにとって無効な文字も含まれているため、URLエンコーディングする必要があります。
Polyline Encodingに含まれる文字はASCIIコードの64(@
) ~ 126(~
)でその中でURLにとって無効な文字は
に載っています。
ちなみにPolyline EncodingにはURLにとって予約文字となる文字も含まれていますが、その文字はURLエンコーディングしないように注意してください。
これを踏まえて遠軽町から湧別町までのアクティビティを地図に表示するURLは
https://maps.googleapis.com/maps/api/staticmap?size=400x400&path=weight:5%7Ccolor:blue%7Cenc:wuakGe%60akZ%5B%60@SKe@FqBx@uB%60AoAt@YKf@A~@i@XDbAbGQPGh@sGdIyDfFuE%60GkBtBiF~GqDhEwBjAiS%7CEcC~@_Ax@aGdH%7DDhEeBfA%7DCp@aMj@mCSiDiA%7Bp@aXaA_AgEyGqAuAaz@cc@_C_B%7BF%7BFoBkAiCy@oDy@cCWgE@aCZsC~@yA%60AcBrBwCdF%7B@dAsAhA%7BAp@uAZuC@kBWeDy@aAa@qMsDe%7D@cX%7BDa@eCB_BPyCr@_A%5EgRtJqCbAaDb@%7DBAcHu@oCIOUI?OTcFX_E%7C@uBz@_NdHmCzB%7BCbEqMpTkDhEkE%7CDwVzRoEjE%7DAxBsC%7CFyBdG_ClIq@~AqBvC%7BArAgCrAs%5EbKgBz@cA%7C@sAnBcAnCk@fCSWMi@aCeAwGuDo%5EaReKwFwR%7BJ%7BMeHaAq@%7DAm@cC%7BAeDuA%5DDMj@DFDw@Wu@eDwAwFcDkMqGc@GsAFcA%60@gGvAyBl@yC~@%7D@b@c@t@aAhA%5Br@kDfEuAp@mH%7CBkAEg@m@mB%7BFS%5D_Ao@yAo@eBgAcIyDy@o@kDmB_@IOSwAk@sBsA%7BCqA%7DQ%7DJiBu@y@m@qDkB%7BCsAu@m@mHoDyCiBkKkFaE_C_Bs@wIoEcBiAcCcAsC?o@PsHd@cEf@iIf@yEf@oKVoFWqDa@oFiAoIgCaGsAwAUeCAgDl@sBdAgEnCqFfCyC%60A%7BEx@wENoD?eCOoFy@eHcCoE_CkDgCeEiEiCgDyVob@qCeF_C_EUQ_H%7DLmKqQaC%7DCyAoA_FiC%5BG%7BByAsAi@%7DEsCkHoDiH_EaKcFgAu@uOaIwEkCmLkGiOyHGM%7D@%5Dq@g@mAe@gGiDgO_I%7BBaAeHsD%7DBuAi@QaRaKq@Wu@i@%7B@WaCyAcAYyAcAaAYYYcA_@c@_@s@Wa@i@iA_@iC%7BA%7BAk@%7B@o@iEmBmCcBuB%7B@aDqB%5BAWTHC?YUYiA_@yBoAeAw@i@MWYgCoAoAg@m@i@aCiAc@_@w@QsC%7DAsA%7D@iBu@cGiDyAg@%5BYgD_B%5BY_@KwByAwCuAUWgFmC_DwAgAm@_Am@yAm@MSkDeBQUm@OmNoHmBkAy@WaAq@kAe@%7BDsBgAu@%7BBcAsDwBiD%7DAEMaAk@eCkAwA%7D@%5BGyBuAuKiFe@_@aIiEa@IkAk@k@g@u@WmB%7B@w@o@_By@kA_@g@e@i@Sg@a@w@WgB_A%5D%5By@%5DSYgA_@aBcA_AY%5CBFc@C_@Ji@Tu@NkAfAwET@zBpAb@@BFOT?L&key=[API_KEY]&signature=[SIGNATURE]
です。
これでリクエストすると
Stravaのスクショ通りの画像を取得できました。
おまけ
Polyline Encoding Algorithmについて
緯度経度からPolyline Encodingする具体的なフローについてです。
公式がPolyline Encodingの具体的な手順について載せています。
小田原駅(35.2564493,139.1532045)から国府津駅(35.2811428,139.2140066)までの線をPolyline Encodingする例を考えます。
小田原駅から考えます。
まずは105して丸めます。
3525644,13915320
次に2の補数の2進数に変換します(32bit)。正の数なので変わりないです。
緯度: 00000000001101011100110000001100
経度: 00000000110101000101010010111000
1ビットの左へのシフト演算によって値を2倍する。
緯度: 00000000011010111001100000011000
経度: 00000001101010001010100101110000
この時点で負の数の場合はビットを反転させますが、今回は正の数なので操作はありません。
緯度: 00000000011010111001100000011000
経度: 00000001101010001010100101110000
変換した2進数を右から5ビットずつに分割します。溢れた分は消します。
緯度: 00000 00110 10111 00110 00000 11000
経度: 00000 11010 10001 01010 01011 10000
分割した5ビットの1単位をチャンクと呼びます。チャンクを逆順にします。
緯度: 11000 00000 00110 10111 00110 00000
経度: 10000 01011 01010 10001 11010 00000
左のチャンクから順番に0x20の論理和演算をします。 最後のチャンク(=次以降のチャンクが0)の場合はチャンク先頭に0を付加します。
緯度: 111000 100000 100110 110111 000110 000000
経度: 110000 101011 101010 110001 011010 000000
それぞれのチャンクを10進数に戻します。
緯度: 56 32 38 55 6
経度: 48 43 42 49 26
それぞれの値に63を足します。
緯度: 119 95 101 118 69
経度: 111 106 105 112 89
それぞれの値をASCIIコード*3として文字に変換します。
すると緯度経度それぞれ
緯度: w_evE
経度: ojipY
で表されます。
よって、小田原駅の緯度経度をPolyline Encodingすると
w_evEojipY
です。
実際にAPIを叩いてみると
https://maps.googleapis.com/maps/api/staticmap?&size=600x600&path=weight:5%7Ccolor:red%7Cenc:w_evEojipY&key=[API_KEY]&signature=[SIGNATURE]
小田原駅周辺の画像を取れました。
次に国府津駅のPolyline Encodingをしていきます。
入力する緯度経度が複数の場合2つ目以降は一つ前の緯度経度との差分を考えます。 つまり、 (35.2811428,139.2140066) - (35.2564493,139.1532045) = (0.00246935, 0.00608021) です。
105して丸めます。
2469,6080
次に2の補数の2進数に変換します(32bit)。
緯度: 00000000000000000000100110100101
経度: 00000000000000000001011111000000
1ビットの左へのシフト演算によって値を2倍します。
緯度: 00000000000000000001001101001010
経度: 00000000000000000010111110000000
正の数なのでビット反転はしません。
緯度: 00000000000000000001001101001010
経度: 00000000000000000010111110000000
変換した2進数を右から5ビットずつに分割する。溢れた分は消します。
緯度: 00000 00000 00000 00100 11010 01010
経度: 00000 00000 00000 01011 11100 00000
チャンクを逆順にします。
緯度: 01010 11010 00100 00000 00000 00000
経度: 00000 11100 01011 00000 00000 00000
左のチャンクから順番に0x20の論理和演算をします。 最後のチャンク(=次以降のチャンクが0)の場合はチャンク先頭に0を付加します。
緯度: 101010 111010 000100 00000 00000 00000
経度: 100000 111100 001011 00000 00000 00000
それぞれのチャンクを10進数に戻します。
緯度: 42 58 4
経度: 32 60 11
それぞれの値に63を足します。
緯度: 105 121 67
経度: 95 123 74
ASCIIコードに変換すると、
iyC_{J
です。
URLに載せるときはURLエンコーディングして
iyC_%7BJ
になります。
小田原駅のPolyline Encodingと合わせると、
https://maps.googleapis.com/maps/api/staticmap?&size=600x600&path=weight:5%7Ccolor:red%7Cenc:w_evEojipYiyC_%7BJ&key=[API_KEY]&signature=[SIGNATURE]
*1:base64の違いは https://qiita.com/kunihiros/items/2722d690b1525813c45e を参考にしました
*2:HMAC-SHA1についてはhttps://e-words.jp/w/HMAC.htmlを参考にしました
*3:https://www.k-cube.co.jp/wakaba/server/ascii_code.htmlを参考にしました。