Calculate angle (clockwise) between two points
Asked Answered
O

8

29

I have been not using math for a long time and this should be a simple problem to solve.

Suppose I have two points A: (1, 0) and B: (1, -1).

I want to use a program (Python or whatever programming language) to calculate the clockwise angle between A, origin (0, 0) and B. It will be something like this:

angle_clockwise(point1, point2)

Note that the order of the parameters matters. Since the angle calculation will be clockwise:

  • If I call angle_clockwise(A, B), it returns 45.
  • If I call angle_clockwise(B, A), it returns 315.

In other words, the algorithm is like this:

  1. Draw a line (line 1) between the first point param with (0, 0).
  2. Draw a line (line 2) between the second point param with (0, 0).
  3. Revolve line 1 around (0, 0) clockwise until it overlaps line 2.
  4. The angular distance line 1 traveled will be the returned angle.

Is there any way to code this problem?

Orban answered 30/7, 2015 at 23:1 Comment(4)
Read en.wikipedia.org/wiki/Atan2 and note atan2 is in docs.python.org/2/library/math.htmlAronarondel
the question is asking about the code to implement atan2,but not the concept of atan2,why suggest to see more about atan2?Surpass
"If I call angle_clockwise(B, A), it returns 335" - surely you mean 315 (360 - 45)?Creed
Whoops... Yes I mean 315. Now you see how long I haven't been using math :DOrban
C
17

Use the inner product and the determinant of the two vectors. This is really what you should understand if you want to understand how this works. You'll need to know/read about vector math to understand.

See: https://en.wikipedia.org/wiki/Dot_product and https://en.wikipedia.org/wiki/Determinant

from math import acos
from math import sqrt
from math import pi

def length(v):
    return sqrt(v[0]**2+v[1]**2)
def dot_product(v,w):
   return v[0]*w[0]+v[1]*w[1]
def determinant(v,w):
   return v[0]*w[1]-v[1]*w[0]
def inner_angle(v,w):
   cosx=dot_product(v,w)/(length(v)*length(w))
   rad=acos(cosx) # in radians
   return rad*180/pi # returns degrees
def angle_clockwise(A, B):
    inner=inner_angle(A,B)
    det = determinant(A,B)
    if det<0: #this is a property of the det. If the det < 0 then B is clockwise of A
        return inner
    else: # if the det > 0 then A is immediately clockwise of B
        return 360-inner

In the determinant computation, you're concatenating the two vectors to form a 2 x 2 matrix, for which you're computing the determinant.

Cotton answered 30/7, 2015 at 23:45 Comment(2)
How would this generalize to n-dims?Intern
Does this handle division by zero?Media
C
41

Numpy's arctan2(y, x) will compute the counterclockwise angle (a value in radians between -π and π) between the origin and the point (x, y).

You could do this for your points A and B, then subtract the second angle from the first to get the signed clockwise angular difference. This difference will be between -2π and 2π, so in order to get a positive angle between 0 and 2π you could then take the modulo against 2π. Finally you can convert radians to degrees using np.rad2deg.

import numpy as np

def angle_between(p1, p2):
    ang1 = np.arctan2(*p1[::-1])
    ang2 = np.arctan2(*p2[::-1])
    return np.rad2deg((ang1 - ang2) % (2 * np.pi))

For example:

A = (1, 0)
B = (1, -1)

print(angle_between(A, B))
# 45.

print(angle_between(B, A))
# 315.

If you don't want to use numpy, you could use math.atan2 in place of np.arctan2, and use math.degrees (or just multiply by 180 / math.pi) in order to convert from radians to degrees. One advantage of the numpy version is that you can also pass two (2, ...) arrays for p1 and p2 in order to compute the angles between multiple pairs of points in a vectorized way.

Creed answered 30/7, 2015 at 23:16 Comment(5)
arctan2(y,x) computes the counterclockwise angle (in readians) between the x-axis and the vector (x,y). To define an angle, you need three points or two vectors, not just two points.Bostow
@Bostow OP is asking for the clockwise angle between the vector from the origin to point A, and the vector from the origin to point B. We have three points and two vectors, so the angle is well-defined.Creed
"This will be between -π and π" This is not true - the angle will be be between -2π and 2πOrban
@Orban You're right - that only refers to the output of np.arctan2 and not the difference of two such angles. I've updated the wording to clarify this.Creed
you are amazing.Myopic
C
17

Use the inner product and the determinant of the two vectors. This is really what you should understand if you want to understand how this works. You'll need to know/read about vector math to understand.

See: https://en.wikipedia.org/wiki/Dot_product and https://en.wikipedia.org/wiki/Determinant

from math import acos
from math import sqrt
from math import pi

def length(v):
    return sqrt(v[0]**2+v[1]**2)
def dot_product(v,w):
   return v[0]*w[0]+v[1]*w[1]
def determinant(v,w):
   return v[0]*w[1]-v[1]*w[0]
def inner_angle(v,w):
   cosx=dot_product(v,w)/(length(v)*length(w))
   rad=acos(cosx) # in radians
   return rad*180/pi # returns degrees
def angle_clockwise(A, B):
    inner=inner_angle(A,B)
    det = determinant(A,B)
    if det<0: #this is a property of the det. If the det < 0 then B is clockwise of A
        return inner
    else: # if the det > 0 then A is immediately clockwise of B
        return 360-inner

In the determinant computation, you're concatenating the two vectors to form a 2 x 2 matrix, for which you're computing the determinant.

Cotton answered 30/7, 2015 at 23:45 Comment(2)
How would this generalize to n-dims?Intern
Does this handle division by zero?Media
G
8

Here's a solution that doesn't require cmath.

import math

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

v1 = Vector(0, 1)
v2 = Vector(0, -1)

v1_theta = math.atan2(v1.y, v1.x)
v2_theta = math.atan2(v2.y, v2.x)

r = (v2_theta - v1_theta) * (180.0 / math.pi)

if r < 0:
    r += 360.0

print r
Glasshouse answered 30/7, 2015 at 23:46 Comment(3)
Better not use such a poor approximation for piCantrell
@Cantrell Fixed value of pi.Glasshouse
can use r % 360 at the end. In Python the modulus works as it should be with negatives :)Ahmed
M
6

A verified 0° to 360° solution

It is an old thread, but for me the other solutions didn't work well, so I implemented my own version.

My function will return a number between 0 and 360 (excluding 360) for two points on the screen (i.e. 'y' starts at the top and increasing towards the bottom), where results are as in a compass, 0° at the top, increasing clockwise:

def angle_between_points(p1, p2):
    d1 = p2[0] - p1[0]
    d2 = p2[1] - p1[1]
    if d1 == 0:
        if d2 == 0:  # same points?
            deg = 0
        else:
            deg = 0 if p1[1] > p2[1] else 180
    elif d2 == 0:
        deg = 90 if p1[0] < p2[0] else 270
    else:
        deg = math.atan(d2 / d1) / pi * 180
        lowering = p1[1] < p2[1]
        if (lowering and deg < 0) or (not lowering and deg > 0):
            deg += 270
        else:
            deg += 90
    return deg
Mischiefmaker answered 12/11, 2020 at 16:3 Comment(0)
C
2

Check out the cmath python library.

>>> import cmath
>>> a_phase = cmath.phase(complex(1,0))
>>> b_phase = cmath.phase(complex(1,-1))
>>> (a_phase - b_phase) * 180 / cmath.pi
45.0
>>> (b_phase - a_phase) * 180 / cmath.pi
-45.0

You can check if a number is less than 0 and add 360 to it if you want all positive angles, too.

Cockswain answered 30/7, 2015 at 23:12 Comment(0)
A
1

Chris St Pierre: when using your function with:

A = (x=1, y=0)
B = (x=0, y=1)

This is supposed to be a 90 degree angle from A to B. Your function will return 270.

Is there an error in how you process the sign of the det or am I missing something?

Acidophil answered 13/2, 2018 at 14:18 Comment(1)
This does not answer the question. Once you receive enough reputation, you can comment on questions. In the meantime, please attempt to answer questions that do not require clarification from its author.Largo
T
0

A formula that calculates an angle clockwise, and is used in surveying:

f(E,N)=pi()-pi()/2*(1+sign(N))* (1-sign(E^2))-pi()/4*(2+sign(N))*sign(E)

     -sign(N*E)*atan((abs(N)-abs(E))/(abs(N)+abs(E)))

The formula gives angles from 0 to 2pi,start from the North and

is working for any value of N and E. (N=N2-N1 and E=E2-E1)

For N=E=0 the result is undefined.

Troth answered 15/11, 2018 at 10:4 Comment(0)
T
0

in radians, clockwise, from 0 to PI * 2

static angle(center:Coord, p1:Coord, p2:Coord) {
    var a1 = Math.atan2(p1.y - center.y, p1.x - center.x);
    var a2 = Math.atan2(p2.y - center.y, p2.x -center.x);
    a1 = a1 > 0 ? a1 : Math.PI * 2 + a1;//make angle from 0 to PI * 2
    a2 = a2 > 0 ? a2 : Math.PI * 2 + a2;
    if(a1 > a2) {
        return a1 - a2;
    } else {
        return Math.PI * 2 - (a2 - a1)
    }
}
Timeworn answered 28/5, 2021 at 14:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.