線と水平軸の間の角度を計算する方法は?


247

プログラミング言語(Python、C#など)では、線と水平軸の間の角度を計算する方法を決定する必要がありますか?

画像が私が望むものを最もよく表していると思います:

これを説明できる言葉はありません

(P1 x、P1 y)と(P2 x、P2 y)が与えられると、この角度を計算する最良の方法は何ですか?原点は左上にあり、正の象限のみが使用されます。


回答:


387

最初に、始点と終点の違いを見つけます(ここでは、線が無限に伸びていて、特定の点から始まらないため、これは「線」ではなく、有向線分です。)

deltaY = P2_y - P1_y
deltaX = P2_x - P1_x

次に、角度を計算します(これはの正のX軸P1からの正のY軸までP1)。

angleInDegrees = arctan(deltaY / deltaX) * 180 / PI

ただしarctan、この方法で差を分割すると、角度がどの象限にあるかを区別するために必要な区別がなくなるため、理想的ではない可能性があります(以下を参照)。言語にatan2関数が含まれている場合は、代わりに以下を使用してください。

angleInDegrees = atan2(deltaY, deltaX) * 180 / PI

EDIT(2017年2月22日):一般的に、しかし、呼び出すatan2(deltaY,deltaX)ための適切な角度を取得するだけcossin洗練かもしれません。そのような場合、代わりに次のことを行うことができます。

  1. (deltaX, deltaY)ベクトルとして扱います。
  2. そのベクトルを単位ベクトルに正規化します。これを行うには、長さが0でない限り、ベクトルの長さ()で除算deltaXします。deltaYsqrt(deltaX*deltaX+deltaY*deltaY)
  3. その後deltaX、ベクトルと水平軸の間の角度のコサインになります(で正のX軸から正のY軸に向かう方向P1)。
  4. そしてdeltaY今、その角度のサインになります。
  5. ベクトルの長さが0の場合、ベクトルと水平軸の間に角度はありません(そのため、意味のあるサインとコサインはありません)。

編集(2017年2月28日):正規化しなくても(deltaX, deltaY)

  • の記号はdeltaX、手順3で説明したコサインが正か負かを示します。
  • の記号はdeltaY、ステップ4で説明したサインが正か負かを示します。
  • 兆候deltaXとはdeltaY角度が正のX軸のに関連して、である象限を教えてくれますP1
    • +deltaX+deltaY:0〜90度。
    • -deltaX+deltaY:90〜180度。
    • -deltaX-deltaY:180〜270度(-180〜-90度)。
    • +deltaX-deltaY:270〜360度(-90〜0度)。

ラジアンを使用したPythonでの実装(2015年7月19日、Eric Leschinskiが私の回答を編集して提供):

from math import *
def angle_trunc(a):
    while a < 0.0:
        a += pi * 2
    return a

def getAngleBetweenPoints(x_orig, y_orig, x_landmark, y_landmark):
    deltaY = y_landmark - y_orig
    deltaX = x_landmark - x_orig
    return angle_trunc(atan2(deltaY, deltaX))

angle = getAngleBetweenPoints(5, 2, 1,4)
assert angle >= 0, "angle must be >= 0"
angle = getAngleBetweenPoints(1, 1, 2, 1)
assert angle == 0, "expecting angle to be 0"
angle = getAngleBetweenPoints(2, 1, 1, 1)
assert abs(pi - angle) <= 0.01, "expecting angle to be pi, it is: " + str(angle)
angle = getAngleBetweenPoints(2, 1, 2, 3)
assert abs(angle - pi/2) <= 0.01, "expecting angle to be pi/2, it is: " + str(angle)
angle = getAngleBetweenPoints(2, 1, 2, 0)
assert abs(angle - (pi+pi/2)) <= 0.01, "expecting angle to be pi+pi/2, it is: " + str(angle)
angle = getAngleBetweenPoints(1, 1, 2, 2)
assert abs(angle - (pi/4)) <= 0.01, "expecting angle to be pi/4, it is: " + str(angle)
angle = getAngleBetweenPoints(-1, -1, -2, -2)
assert abs(angle - (pi+pi/4)) <= 0.01, "expecting angle to be pi+pi/4, it is: " + str(angle)
angle = getAngleBetweenPoints(-1, -1, -1, 2)
assert abs(angle - (pi/2)) <= 0.01, "expecting angle to be pi/2, it is: " + str(angle)

すべてのテストに合格。https://en.wikipedia.org/wiki/Unit_circleを参照してください


35
これを見つけてJAVASCRiPTを使用している場合は、Math.sinとMath.cosがラジアンを取るので、結果を度数に変換する必要がないことに注意してください。したがって、* 180 / PIビットは無視してください。それを見つけるのに4時間かかりました。:)
sidonaldson 2013年

垂直軸に沿った角度を計算するために何を使用すべきですか?
ZeMoon、2014年

3
@akashg: 90 - angleInDegrees
jbaums 2014年

なぜ90-angleInDegreesを実行する必要があるのですか。理由はありますか?同じことを明確にしてください。
Praveen Matanam 2014

2
@sidonaldsonこれは単なるJavaScriptではなく、C、C#、C ++、Javaなどです。実際、大多数の言語では、数学ライブラリが主にラジアンで動作していると思います。デフォルトで学位のみをサポートする言語はまだ見ていません。
Pharap 2014

50

申し訳ありませんが、ピーターの答えは間違っていると思います。y軸はページを下に行くことに注意してください(グラフィックでは一般的です)。そのため、deltaYの計算を逆にする必要があります。そうしないと、間違った答えが返されます。

考慮してください:

System.out.println (Math.toDegrees(Math.atan2(1,1)));
System.out.println (Math.toDegrees(Math.atan2(-1,1)));
System.out.println (Math.toDegrees(Math.atan2(1,-1)));
System.out.println (Math.toDegrees(Math.atan2(-1,-1)));

与える

45.0
-45.0
135.0
-135.0

したがって、上記の例でP1が(1,1)でP2が(2,2)である場合(Yがページの下に向かって増加するため)、上記のコードは、示されている例では45.0度になりますが、これは誤りです。deltaY計算の順序を変更すると、正しく機能します。


3
あなたが提案したように私はそれを逆にしました、そして私の回転は逆でした。
悪魔の擁護者

1
私のコードでは、これを次のように修正しています: double arc = Math.atan2(mouse.y - obj.getPy(), mouse.x - obj.getPx()); degrees = Math.toDegrees(arc); if (degrees < 0) degrees += 360; else if (degrees > 360) degrees -= 360;
Marcus Becker

それはあなたの角度が入っている円の四分の一に依存します:最初の四分の一(最大90度)の場合、deltaXとdeltaY(Math.abs)に正の値を使用し、2番目(90-180)に使用しますネゲート第三(180から270)でDELTAXの抽象値は、両方のDELTAXとdeltaY移動を否定し、第四(270から360)ネゲートのみdeltaY移動をint型-の下に私の答えを参照してください
mamashare

1

私はPythonでうまく機能している解決策を見つけました!

from math import atan2,degrees

def GetAngleOfLineBetweenTwoPoints(p1, p2):
    return degrees(atan2(p2 - p1, 1))

print GetAngleOfLineBetweenTwoPoints(1,3)

1

正確な質問を考えると、正の軸が下に移動することを意味する「特別な」座標系(画面やインターフェイスビューなど)に置くと、この関数を次のように適合させ、Y座標を負にする必要があります。

Swift 2.0の例

func angle_between_two_points(pa:CGPoint,pb:CGPoint)->Double{
    let deltaY:Double = (Double(-pb.y) - Double(-pa.y))
    let deltaX:Double = (Double(pb.x) - Double(pa.x))
    var a = atan2(deltaY,deltaX)
    while a < 0.0 {
        a = a + M_PI*2
    }
    return a
}

この関数は、質問に対する正しい答えを与えます。答えはラジアンであるため、角度を度数で表示するための使用法は次のとおりです。

let p1 = CGPoint(x: 1.5, y: 2) //estimated coords of p1 in question
let p2 = CGPoint(x: 2, y : 3) //estimated coords of p2 in question

print(angle_between_two_points(p1, pb: p2) / (M_PI/180))
//returns 296.56

0

参照「ピーターO」に基づく..これはJavaバージョンです

private static final float angleBetweenPoints(PointF a, PointF b) {
float deltaY = b.y - a.y;
float deltaX = b.x - a.x;
return (float) (Math.atan2(deltaY, deltaX)); }

0

MATLAB関数:

function [lineAngle] = getLineAngle(x1, y1, x2, y2) 
    deltaY = y2 - y1;
    deltaX = x2 - x1;

    lineAngle = rad2deg(atan2(deltaY, deltaX));

    if deltaY < 0
        lineAngle = lineAngle + 360;
    end
end

0

0から2piまでの角度の式。

x = x2-x1とy = y2-y1があります。式は、

xおよびyの任意の値。x = y = 0の場合、結果は未定義です。

f(x、y)= pi()-pi()/ 2 *(1 + sign(x))*(1-sign(y ^ 2))

     -pi()/4*(2+sign(x))*sign(y)

     -sign(x*y)*atan((abs(x)-abs(y))/(abs(x)+abs(y)))

0
deltaY = Math.Abs(P2.y - P1.y);
deltaX = Math.Abs(P2.x - P1.x);

angleInDegrees = Math.atan2(deltaY, deltaX) * 180 / PI

if(p2.y > p1.y) // Second point is lower than first, angle goes down (180-360)
{
  if(p2.x < p1.x)//Second point is to the left of first (180-270)
    angleInDegrees += 180;
  else //(270-360)
    angleInDegrees += 270;
}
else if (p2.x < p1.x) //Second point is top left of first (90-180)
  angleInDegrees += 90;

あなたのコードは意味がありません:else(270-360)..何ですか?
WDUK、

0
import math
from collections import namedtuple


Point = namedtuple("Point", ["x", "y"])


def get_angle(p1: Point, p2: Point) -> float:
    """Get the angle of this line with the horizontal axis."""
    dx = p2.x - p1.x
    dy = p2.y - p1.y
    theta = math.atan2(dy, dx)
    angle = math.degrees(theta)  # angle is in (-180, 180]
    if angle < 0:
        angle = 360 + angle
    return angle

テスト中

テストでは、仮説にテストケースを生成させます。

ここに画像の説明を入力してください

import hypothesis.strategies as s
from hypothesis import given


@given(s.floats(min_value=0.0, max_value=360.0))
def test_angle(angle: float):
    epsilon = 0.0001
    x = math.cos(math.radians(angle))
    y = math.sin(math.radians(angle))
    p1 = Point(0, 0)
    p2 = Point(x, y)
    assert abs(get_angle(p1, p2) - angle) < epsilon
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.