Rubyで「角度」や「座標」を扱う際に必要な、最低限の知識　＋　Aさんから見てB君はどの方向にいる？のアルゴリズム



先日Rubyで座標や角度を扱う機会があったので、それらを扱う上で必要となる全知識をコンパクトにして、丁寧にまとめてみました。
後半には例として「Aさんから見て、B君はどの方角にいるか」みたいなのを8方位単位で調べられる簡単なコードを載せています。参考になれば幸いです。(バージョン：ruby 2.5.1)

![su.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536363/633bfecd-5357-a915-e5df-d412a441be20.png)


# 角度には、よく使われる2つの単位がある

長さを表す単位にも「メートル」や「フィート」等、規格の異なる表し方があるように、角度にも複数の表し方があります。私達が角度を表す際、一般的によく使うのは90°(90度)みたいな感じですよね。しかし__Rubyが用意しているモジュール上では、ラジアン（rad）という単位が用いられています__。

#### 度（ ° ）の考え方

この単位は、__1周を360°__としています。なので、例えば「右(or左)に360°回転」すると、元と全く同じ方向に戻ることが出来ます。

#### ラジアン（ rad ）の考え方

この単位は、__1周を2×π rad__ としています。なので、例えば「右(or左)に2×π rad 回転」すると、元と全く同じ方向に戻ることが出来ます。（π = 円周率 ≒ 3.14）

私のように学生の頃の記憶を失ってしまった方のために申し上げると、πというのは、円の直径に対して円周の長さがどれくらいあるか？の比率であり、その比率は約3.14倍と決まっています。円の直径が1cmだとしたら、その円の円周は 1×π cm 、つまり約3.14cmになります。
[π(円周率)の詳細についてはコチラ](https://ja.wikipedia.org/wiki/%E5%86%86%E5%91%A8%E7%8E%87)

#### よって

以下のように度(°)をラジアン(rad)に変換することが出来ます。

__0°__ = 0 × π = __0.00000__… rad
__45°__ = (2 × π) ÷ 8 = __0.78539__… rad　（45°は、"2×π"の8等分）
__90°__ = (2 × π) ÷ 4 = __1.57079__… rad　（90°は、"2×π"の4等分）
__180°__ = (2 × π) ÷ 2 = __3.14159__… rad　（180°は、"2×π"の2等分）
__270°__ = (2 × π) ÷ 4 × 3 = __4.71238__… rad　（270°は、"2×π"の3/4）
__360°__ = (2 × π) = __6.28318__… rad　（360°は、"2×π"）

少々回りくどい説明になってしまいましたが、__180(°) = π (rad)__ という基準を抑えておけば覚えやすいですね。　

#### Rubyの世界では π = 「 Math::PI 」


ここでひとつRubyの必須知識を抑えましょう。π(≒3.14)をコード上で表現するには、「Math」というモジュールの助けを借りる必要があります。
モジュールを呼び出す方法は、コード上部にて`include Math`を表記します。モジュールを呼び出しておけば、`PI`と表記するだけで、πを表現することが出来ます。

```ruby
include Math

puts PI
# 出力結果：：　　3.141592653589793
```
ファイル全体ではなく単発でモジュールを呼び出したいときは、以下の方法で呼び出します(include Math不要バージョン)。

```ruby
puts Math::PI
# 出力結果：：　　3.141592653589793
```

参考… 以下リンクでも、度(°)⇔ラジアン(rad)の相互変換方法について紹介されてます。
[「ラジアンと度の変換をするRubyコード」by @niwasawa さん](https://qiita.com/niwasawa/items/620c1a2dcf385a85810d)


# 座標から角度を求める「 Math::atan2 」の使い方

![location.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536363/7cc15243-6d64-3564-907b-95fce1cc6578.png)

上記のようにx軸とy軸が用意されている中で、座標(xとyの値)が与えられていれば、前述のMathモジュールが用意している別の関数`atan2`を利用して、角度(rad)を求めることができます。

```ruby
include Math

def radian(x, y)
  return atan2(y, x)
  # 引数xと引数yの順序が逆なことに注意
end

puts radian(5, 3)
# 出力結果::   0.5404195002705842
```

これだとあまり馴染みのないラジアン（rad）による出力結果なので、以下では度(°)に変換すべく「÷ π × 180」をメソッド内に追記し、最後に四捨五入もしてみます。

```ruby
include Math

def degree(x, y)
  return atan2(y, x)/PI*180
end

puts degree(5, 3).round
# 出力結果::   31
```
無事に、３１度という角度を求めることが出来ました。

# Aさんから見て、B君はどの方角にいるか？


![a_b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536363/8f8e91ff-3aef-1990-315a-52ec4d934639.png)

上の図のようにAさんの座標とB君の座標を与えられた場合に、8方位上どの方角にいるかをアンサー出来るよう、コードを作ってみます。ついでに、2人の間の距離も図ってみたいと思います。(距離の単位: m とします)

## 仕様
Aさんの座標(x, y)と、B君の座標(x, y)を入力
↓
Aさんから見てB君がいる方角と、距離を出力する

```terminal:入力
Aさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Aさんの座標は? ◯ ◯ ←ココに入力
B君の座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
B君の座標は? ◯ ◯ ←ココに入力

```

```terminal:出力
B君はAさんから見て◆◆の方向にいて、▲▲m離れています。
```

```terminal:例外処理
二人は同じ場所にいます。
```
## ポイント1　Aさんを起点(中心)にして考える
以下イメージのように、"Aさんから見て"どの方向にいるのかを調べる為には、Aさんを起点（中心）にして考えます。
![slide.mov.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/536363/d05bc31f-6257-854e-d844-70afa71dbb89.gif)
この場合だとAさんを中心に持っていく為にはAさんをｘ方向に-3m、y方向に＋2m移動させればOKなので、同じくB君にも同距離を移動してもらいます。

A(3, -2)　xを-3、yを+2する　→　A(0, 0) = 中心にこれました！
同様に…
B(-2, -4)　xを-3、yを+2する　→　__B(-5, -2)　この座標の方角を`Math::atan2`で調べればOK！__ 

## ポイント2　`Math::atan2`の返り値の範囲
今回は全方位を調べます。`atan2`の活用によって、0°〜360°の間の値が得られることを期待しましたが、実は__`atan2`が返してくる値の範囲は -π 〜 π (度数でいうと-180°〜180°)と決まっています__。時計の針でいう「３時〜９時の間」の場合だと、マイナスの値が帰ってきちゃう…ということですね。

この仕様に合わせてアルゴリズムを組んでも問題は無いのですが、勉強の為、得られた角度値を__こちらの都合の良い値に変換する方法__をとってみたいと思います。
[参考：「Rubyで角度を(0〜359度の)正の角度に変換する」by @masassiez さん](https://qiita.com/masassiez/items/fdfeadb638824c21d4c3)

```ruby
# -180 〜 180 の間にある値を、 0 〜 360 の間の値に変換する方法(-120を例にした場合)

(-120).modulo(360)
# もしくは
-120 % 360

# これにより、240 に変換される
```

上記の`%`も`modulo`も、やっていることは全く同じです。360で割った際の剰余(あまり)を求めることで、自動的に 0〜360 の範囲内に変換されることになります。(ちなみにmoduloは、include Mathしなくても使えます)


## ポイント3　距離は三平方の定理を用いる
2点間A(x, y)とB(x, y)の距離を求める方法は、[三平方の定理](https://www.try-it.jp/keyword_articles/25/)を使いましょう。

```math
AB= \sqrt{(x_b−x_a)^2+(y_b−y_a)^2}
```
これをRubyでやると、以下みたいになります。

```ruby
def distance(a_x, a_y, b_x, b_y)
  return ((b_x - a_x)**2 + (b_y - a_y)**2)**(1/2.0)
end

# 　**2　← ２乗しています
# 　**(1/2.0)　← ルート(平方根)しています
```


# 完成コード

```ruby:sampl.rb(完成)

# 今回は atan2 や PI を使いたいので、最初に include Math を宣言します
include Math

# 角度を求めるメソッド
def degree(x, y)
  (atan2(y, x)/PI*180)%360
end

# 方角を求めるメソッド
def compass(degree)
  if degree > 15 && degree < 75
     "北東"
  elsif degree >= 75 && degree <= 105
     "北"
  elsif degree > 105 && degree < 165
     "北西"
  elsif degree >= 165 && degree <= 195
     "西"
  elsif degree > 195 && degree < 255
     "南西"
  elsif degree >= 255 && degree <= 285
     "南"
  elsif degree > 285 && degree < 345
     "南東"
  else
     "東"
  end
end

# 2点間の距離を求めるメソッド
def distance(a_x, a_y, b_x, b_y)
   ((b_x - a_x)**2 + (b_y - a_y)**2)**(1/2.0)
end


puts "Aさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください"
print "Aさんの座標は? "
a_x, a_y = gets.split.map(&:to_i)

puts "Bさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください"
print "Bさんの座標は? "
b_x, b_y = gets.split.map(&:to_i)

if a_x == b_x && a_y == b_y
  puts "二人は同じ場所にいます。"
  exit
end

direction = compass(degree(b_x - a_x, b_y - a_y))
distance = distance(a_x, a_y, b_x, b_y).round(1)

puts "B君はAさんから見て#{direction}の方向にいて、#{distance}m離れています。"
# 2メートル以内は、密です
distance > 2 ? (puts "ソーシャルディスタンスが保たれています。") : (puts "密です！")
```

### 出力結果

```terminal:Aさん(3,-2)、B君(-2,-4)の場合
Aさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Aさんの座標は? 3 -2
Bさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Bさんの座標は? -2 -4

↓ 結果 ↓

B君はAさんから見て南西の方向にいて、5.4m離れています。
ソーシャルディスタンスが保たれています。
```

```terminal:Aさん(-1,-7)、B君(1,-7)の場合
Aさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Aさんの座標は? -1 -7
Bさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Bさんの座標は? 1 -7

↓ 結果 ↓

B君はAさんから見て東の方向にいて、2.0m離れています。
密です！
```

```terminal:Aさん(4,3)、B君(4,3)の場合
Aさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Aさんの座標は? 4 3
Bさんの座標(x, y)を、半角数字で2つ、間に半角スペースを入れて入力してください
Bさんの座標は? 4 3

↓ 結果 ↓

二人は同じ場所にいます。
```

以上、「Rubyで「角度」や「座標」を扱う際に必要な、最低限の知識　＋　Aさんから見てB君はどの方向にいる？のアルゴリズム」でした。ご覧いただき、ありがとうございました！