みかづきブログ・カスタム

基本的にはちょちょいのほいです。

JavaScriptで角度の平均を計算する 🧮

f:id:kimizuka:20210811210028p:plain

ベクトルを合算し、原点からの角度を出すのがセオリーのようです。

関数にするとこんな感じです。

function getAverageAngle(angles = []) {
  let x = 0;
  let y = 0;

  angles.forEach((angle) => {
    x += Math.sin(angle / 180 * Math.PI);
    y += Math.cos(angle / 180 * Math.PI);
  });

  return Math.atan2(x, y) * 180 / Math.PI;
}

引数も返り値もラジアンで管理しても良いのですが、個人的には度の方が直感的なので、度にしました。
引数に角度の配列を渡せば平均値が返ってきます。

getAverageAngle([0, 90]); // => 45
getAverageAngle([0, 30, 60]); // => 29.999999999999996 ≒ 30
getAverageAngle([0, 30, -30, 60, -60]); // => 0

と、こんな感じの結果が返ってきます。(JavaScriptが浮動小数点なので微妙な誤差が出てるのがややこしいですが)

これだけを見ると、単純に合算して割るだけでいいんじゃなかろうかと思います。

getAverageAngle([0, 90]); // => 45、(0 + 90) / 2 と一緒?
getAverageAngle([0, 30, 60]); // => 29.999999999999996 ≒ 30、(0 + 30 + 60) / 3 と一緒?
getAverageAngle([0, 30, -30, 60, -60]); // => 0、(0 + 30 - 30 + 60 - 60) / 5 と一緒?

しかし、180度を超えた値を入れると結果に違いが出てきます。

getAverageAngle([180, 360]); // => -90、(180 + 360) / 2 = 270 なので違う値(同じ角度)

これではメリットがわからないですが、1度と359度の平均を取ると、

getAverageAngle([1, 359]); // => -1.3917046364972999e-15  ≒ 0、(1 + 359) / 2 = 180 なので違う値(違う角度)

単純な合算と違う角度になります。
そしておそらく、この時に期待されるであろう値はベクトルの合算です。

ということで、角度の平均を算出する際はベクトルを合算して原点からの角度を出すのが良いということがわかりました。