開発ノート

車は時速 60 km で走っているのに、ヘッドユニットは速度 0 と報告する

Location.hasSpeed() は true を返すのに、値はずっと 0.0 のまま。後付けの Android ユニットでこの問題がなぜ多いのか、アプリがどのように検出しているのかをご紹介します。

机の上なら、Android で速度を読み取るのはコード 1 行で済みます。ところが後付けのヘッドユニットでは、真っ先に問題が起きるのがこの部分です。

原因は NMEA センテンスにあります

GPS モジュールは NMEA センテンスで OS とやり取りします。位置は $GPGGA、速度と方位は $GPRMC で送られます。安価なモジュールの多くは $GPGGA しか解析しません。地図上に点を表示するにはそれで十分で、たいていのアプリにとってはそれで事足りるからです。

さらに厄介なのは、GPS ドライバが速度フィールドを有効だと宣言したままになっていることです。location.hasSpeed() は true を返し、location.speed は 0.0f を返します。例外も発生せず、エラーフラグも立ちません。この値を信じるアプリは、もっとも一般的な種類の端末で、速度計が止まったままになってしまいます。

「速度がゼロかどうか」を調べるだけでは足りません

赤信号で止まっている車も、速度はゼロです。必要なのは「停車中」と「ドライバの不具合」を区別できる手がかりで、それが連続する測位点どうしの距離です。走っている車なら、ドライバが何と報告していても、測位点は数十メートルずつ離れていきます。

アプリの対処法

SpeedEstimator は 2 つのソースを並行して使います。

  • ドライバのソース — location.speed の値をそのまま使います。
  • 算出ソース — 2 つの測位点間の距離を、その間の時間で割った値です。

通常は、値がより滑らかなドライバのソースを使います。ただし、算出ソースでは車が明らかに動いているのにドライバがゼロを報告し続けている場合、アプリはそのたびにサンプルとして数えます。こうしたサンプルがおよそ 8 回たまると、ドライバに不具合があると判断し、そのセッションのあいだはずっと算出ソースを使います。

2 回ではなく 8 回にしているのは、ノイズの多い測位点が 1 つあるだけで、実際にはない移動距離が生まれてしまうことがあるからです。8 回連続となれば、そうはいきません。

運転者に見える表示

切り替わると、画面には「GPS から計算した速度」と表示されます。これはエラーメッセージではありません。表示されている数値が何にもとづいているかを運転者にお伝えするもので、速度の表示が実際より少し遅れる理由が分かるようにしています。多くのヘッドユニットでは、これがごく普通の状態です。