[GoogleMapsAPI] APIと組み合わせる時のイベントはidleを使う

gmap.jpg

四隅の緯度経度を渡したら範囲内のスポット情報が得られる、というAPIは最近そこら中にあって、

これとGoogleMapsAPIを組み合わせて地図上に何らかのスポットを表示する、というのもそこら中で行われています。

こういうサービスを実現するためには、ユーザーが地図の表示位置を変更した時、新しい位置のスポット情報を取得して表示するためにAPI呼び出しをする必要があります。
この実装例をググると、bounds_changed(領域が変化した時)やdragned(ドラッグが終了した時)イベントのタイミングでAPIを呼び出すようにしている実装が多いのですけど、idleイベントを使うとすごい綺麗に実装することが出来ます。
以下、bounds_changedやdragnedを使った場合に発生する問題と、idleイベントだと何がうれしいのかをまとめました。

bounds_changedを使った時の問題

bounds_changedイベントは、「This event is fired when the viewport bounds have changed.」と定義されていて、地図の表示領域が変更になった場合に発生するイベントです。
このタイミングでAPIを呼び出すようにすれば、ユーザーが地図をドラッグした時も、ズームが変更された時もイベントが発生するので、わりといい感じに動くように思えます。

bounds_changedイベントの問題は、発生頻度が多すぎることです。bounds_changedは領域が変わるたびに呼ばれるので、ユーザーが地図をドラッグすると、一秒に数回の勢いでbounds_changedイベントが発生し続けます。
つまり、単純に実装すると、秒間数回のペースでAPIを呼び出し続けることになります。サーバ負荷の観点から絶対止めてほしいですし、ブラウザ側だって、そんな頻度で結果が帰ってきたら処理しきれなくてブラウザが固まってしまうことが予想されます。

カウンタをもうけて10回に一回だけAPIを呼び出すとか、タイマーを使って何秒に一回だけAPIを呼び出すとか、やってやれないことはないのですが、ずいぶん複雑なコードになってしまいます。

dragendを使った場合の問題

dragendイベントでAPIを呼び出すという実装も考えられます。ドラッグ終了時に一回だけ発生するので、その点はいいのですけど、今度はイベントの発生が少なすぎて弊害が発生します。

ズーム変更の時スポットが更新されない

さすがにこれは誰でも気づくので、dragendとzoom_changedでAPIを呼び出す実装にする人が多いと思います。

ドラッグが終わるまでAPIが呼び出せない

dragnedイベントはマウスを放すまで発生しないので、地図をドラッグしながら「この辺かなー?」って考えてもスポットが更新されません。ダメじゃないですけど、ユーザーフレンドリーじゃないですよね。

キーボードで地図を移動させた時APIが呼び出せない

そんな人はあまりいないと思うけど、GoogleMapsはキーボードでも操作ができるようになっています。この場合dragendイベントは発生しません。
また、最近のGoogleMapsは駅などをクリックするとその部分が中央に移動して詳細がポップアップするようになっていますが、この移動の場合もdragendイベントは発生しません。

自分でsetCenterイベントを呼んだ時も発生しない

これが最大の問題で、例えば検索フィールドを作って入力された住所の位置に移動するような実装をする場合、ジオコーディングして得られた緯度経度にmap.setCenterすることになると思うのですけど、この場合もdragendイベントは発生しません。
setCenterを呼び出す時は自前でAPI呼び出しをするコードを追加して・・・(後日APIが変更になった時、片方だけ変更しわすれてバグを埋め込むパターン)

dragendを使った場合の問題

こんな具合にして、起こりうる各種問題に場当たりの対応を繰り返すと、複数のイベントを組み合わせてフラグ管理をすることになったり、コード中のあちこちからAPIを呼び出すことになったり、どうしても複雑なコードになってしまいます。
実際僕もそうやって書いていたのですけど、ある時、idleイベントで全部カバーできることに気づきました。

idleイベントは「This event is fired when the map becomes idle after panning or zooming.」と定義されています。

実際ログを入れて試してみるとわかりますが、

  • Mapをnewすると、地図の表示が終わった時点で一回発生します。つまりここで、最初の一回目のAPI呼び出しを行うことが出来ます
  • 地図をドラッグしおわった時に一回発生。ドラッグ中も、ちょっと手を止めるとそこで発生。idleにならないと発生しないので、API呼び出しが多くなりすぎる心配がありません。
  • ズーム変更の時も発生
  • キーボードで地図を動かした時も発生
  • setCenterやsetZoomを呼び出した時も移動直後に発生

と、理想的な挙動を示します。つまり、HTML表示直後の一回もマップ移動時のAPI呼び出しも、全部まとめて

google.maps.event.addListener(self.gmap, 'idle', function() {
//ここでAPI呼び出し
});

としておくだけで意図した動作をさせることが出来ます。