10

I am trying to calculate the angle between two lines in Python. I searched the Internet and found the equation on how to do it. But I don't always get an accurate result. Some of the results are clearly false when other seems correct.

My code is given below:

def angle(pt1, pt2):
    m1 = (pt1.getY() - pt1.getY())/1
    m2 = (pt2.getY() - pt1.getY())/(pt2.getX()-pt1.getX())

    tnAngle = (m1-m2) / (1 + (m1*m2))
    return math.atan(tnAngle)

def calculate(pt, ls):
    i = 2
    for x in ls:
        pt2 = point(x, i)
        i = i + 1
        ang = angle(pt, pt2)*180/math.pi
        ang = ang * (-1)
        print ang


pt = point(3, 1)
ls = [1, 7, 0, 4, 9, 6, 150]

calculate(pt, ls)

The result it produces is:

45.0
0.0
45.0
-75.9637565321
0.0
-63.4349488229
0.0

The problem is that I don't understand why the second result, fifth and the last one are zeroed. They intersect since they share a point and the other point is not duplicated since the value in the array is different.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
razielone
  • 123
  • 1
  • 1
  • 6

3 Answers3

20

Your angle formula will fail if

pt2.getX() == pt1.getX()

(that is, if pt1 and pt2 lie on a vertical line) because you can not divide by zero. (m2, the slope, would be infinite.)

Also

m1 = (pt1.getY() - pt1.getY())/1

will always be zero. So at the very least, your formula could be simplified to the arctan of the slope. However, I wouldn't bother since the formula does not work for all possible points.

Instead, a more robust method (indeed, the standard method) for calculating the angle between two vectors (directed line segments) is to use the dot product formula:

enter image description here

where if a = (x1, y1), b = (x2, y2), then <a,b> equals x1*x2 + y1*y2, and ||a|| is the length of vector a, i.e. sqrt(x1**2 + y1**2).


import math

def angle(vector1, vector2):
    x1, y1 = vector1
    x2, y2 = vector2
    inner_product = x1*x2 + y1*y2
    len1 = math.hypot(x1, y1)
    len2 = math.hypot(x2, y2)
    return math.acos(inner_product/(len1*len2))

def calculate(pt, ls):
    i = 2
    for x in ls:
        pt2 = (x, i)
        i += 1
        ang = math.degrees(angle(pt, pt2))
        ang = ang * (-1)
        print(ang)

pt = (3, 1)
ls = [1,7,0,4,9,6,150]

calculate(pt, ls)
Yui
  • 57
  • 2
  • 9
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • `math.hypot(x1, y1)` is tidier – John La Rooy Nov 05 '12 at 05:14
  • 2
    An exception should be raised because there is no defined angle when either vector has zero length. – unutbu Jan 18 '14 at 21:03
  • I could be wrong but I think there might be a problem with the sign of the result returned by angle(). If the first point is (1.0, 0.0) and the second point is (1.0, -1.0), then shouldn't the sign of the angle be negative (or 315 degrees)? I think this could be accomplished simply by replacing the last line with `return math.copysign(math.acos(inner_product/(len1*len2)), y2)` – Bill Feb 14 '16 at 05:18
  • @Bill: The function above calculates the angle between two vectors. By definition, that angle is always the *smaller* angle, between 0 and pi radians. It has the property that the angle between two vectors does not change under rotation. For example, if we rotate both vectors 180 degrees, `angle((1,0), (1,-1))` still equals `angle((-1,0), (-1,1))`. If we were to change it to your formula, then the angle would change signs. Also, `angle(A, B) == angle(B, A)`. Yours is not commutative. That's okay -- it just sounds like you are looking for something different than the angle between two vectors. – unutbu Feb 14 '16 at 12:57
14

Here is what I ended up using, all using numpy and the range is between - to

import numpy as np
def get_angle(p0, p1=np.array([0,0]), p2=None):
    ''' compute angle (in degrees) for p0p1p2 corner
    Inputs:
        p0,p1,p2 - points in the form of [x,y]
    '''
    if p2 is None:
        p2 = p1 + np.array([1, 0])
    v0 = np.array(p0) - np.array(p1)
    v1 = np.array(p2) - np.array(p1)

    angle = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
    return np.degrees(angle)
dashesy
  • 2,596
  • 3
  • 45
  • 61
7

It looks like you are using Python2, where / will do an integer division if both arguments are int. To get the behaviour that Python3 has, you can put this at the top of the file

from __future__ import division
John La Rooy
  • 295,403
  • 53
  • 369
  • 502