Calculating angles between line segments (Python) using math.atan2

I am working on a spatial analysis problem, and part of this workflow is calculating the angle between the connected line segments.

Each line segment consists of only two points, and each point has a pair of XY coordinates (Cartesian). Here is an image from GeoGebra. I am always interested in getting a positive angle in the range from 0 to 180 . However, I get all kinds of angles depending on the order of the vertices in the input line segments.

enter image description here

The input data I'm working with is represented as tuples of coordinates. Depending on the order of vertex creation, the last / end point for each line segment may be different. Here are some of the cases in Python code. The order of the line segments in which I get them is random, but in the tuple of tuples, the first element is the start point, and the second is the end point. DE line segment, for example, would have ((1,1.5),(2,2)) , and (1,1.5) be the starting point, because it has the first position in the tuple of coordinates.

However, I need to make sure that I will have the same angle between DE,DF and ED,DF , etc.

 vertexType = "same start point; order 1" #X, YXY coords lineA = ((1,1.5),(2,2)) #DE lineB = ((1,1.5),(2.5,0.5)) #DF calcAngle(lineA, lineB,vertexType) #flip lines order vertexType = "same start point; order 2" lineB = ((1,1.5),(2,2)) #DE lineA = ((1,1.5),(2.5,0.5)) #DF calcAngle(lineA, lineB,vertexType) vertexType = "same end point; order 1" lineA = ((2,2),(1,1.5)) #ED lineB = ((2.5,0.5),(1,1.5)) #FE calcAngle(lineA, lineB,vertexType) #flip lines order vertexType = "same end point; order 2" lineB = ((2,2),(1,1.5)) #ED lineA = ((2.5,0.5),(1,1.5)) #FE calcAngle(lineA, lineB,vertexType) vertexType = "one line after another - down; order 1" lineA = ((2,2),(1,1.5)) #ED lineB = ((1,1.5),(2.5,0.5)) #DF calcAngle(lineA, lineB,vertexType) #flip lines order vertexType = "one line after another - down; order 2" lineB = ((2,2),(1,1.5)) #ED lineA = ((1,1.5),(2.5,0.5)) #DF calcAngle(lineA, lineB,vertexType) vertexType = "one line after another - up; line order 1" lineA = ((1,1.5),(2,2)) #DE lineB = ((2.5,0.5),(1,1.5)) #FD calcAngle(lineA, lineB,vertexType) #flip lines order vertexType = "one line after another - up; line order 2" lineB = ((1,1.5),(2,2)) #DE lineA = ((2.5,0.5),(1,1.5)) #FD calcAngle(lineA, lineB,vertexType) 

I wrote a tiny function that takes combinations of lines as arguments and calculates the angle between them. I am using math.atan2 which is best suited for this.

 def calcAngle(lineA,lineB,vertexType): line1Y1 = lineA[0][1] line1X1 = lineA[0][0] line1Y2 = lineA[1][1] line1X2 = lineA[1][0] line2Y1 = lineB[0][1] line2X1 = lineB[0][0] line2Y2 = lineB[1][1] line2X2 = lineB[1][0] #calculate angle between pairs of lines angle1 = math.atan2(line1Y1-line1Y2,line1X1-line1X2) angle2 = math.atan2(line2Y1-line2Y2,line2X1-line2X2) angleDegrees = (angle1-angle2) * 360 / (2*math.pi) print angleDegrees, vertexType 

The output I get is:

 > -299.744881297 same start point; order 1 > 299.744881297 same start point; order 2 > 60.2551187031 same end point; order 1 > -60.2551187031 same end point; order 2 > -119.744881297 one line after another - down; order 1 > 119.744881297 one line after another - down; order 2 > -119.744881297 one line after another - up; line order 1 > 119.744881297 one line after another - up; line order 2 

As you can see, I get different values ​​depending on the order of the vertices in the line segment and the order of the line segments. I tried to process the corners, finding out what the source line, turning the lines, editing the angle, etc. I ended up with a dozen such cases, and at some point they begin to overlap, and I can no longer find out if -119.744 should become 60.255 (acute angle) or leave it as 119.744 (obtuse angle), etc.

Is there any discrete way to handle the output angle values ​​that I get from math.atan2 to get only a positive value in the range of 0 to 180? If not, what other approach should I take?

+6
source share
3 answers

The easiest and most logical way to solve this problem is to use a dot product.

Try this code (I commented on almost everything):

 import math def dot(vA, vB): return vA[0]*vB[0]+vA[1]*vB[1] def ang(lineA, lineB): # Get nicer vector form vA = [(lineA[0][0]-lineA[1][0]), (lineA[0][1]-lineA[1][1])] vB = [(lineB[0][0]-lineB[1][0]), (lineB[0][1]-lineB[1][1])] # Get dot prod dot_prod = dot(vA, vB) # Get magnitudes magA = dot(vA, vA)**0.5 magB = dot(vB, vB)**0.5 # Get cosine value cos_ = dot_prod/magA/magB # Get angle in radians and then convert to degrees angle = math.acos(dot_prod/magB/magA) # Basically doing angle <- angle mod 360 ang_deg = math.degrees(angle)%360 if ang_deg-180>=0: # As in if statement return 360 - ang_deg else: return ang_deg 

Now try your lineA and lineB options and everyone should give the same answer.

+6
source

Too much work. Take the absolute value of the arccosine dot product of two vectors divided by each of the line lengths.

+2
source

An alternative solution according to the formula:

enter image description here

where "m1" is the slope of line 1, and "m2" is the slope of line 2. If line 1 is determined by the points P1 = [x1, y1] and P2 = [x2, y2], then the slope of "m" is equal to:

enter image description here

Using the formulas above, you can find angular in degrees between two lines as follows:

 def slope(x1, y1, x2, y2): # Line slope given two points: return (y2-y1)/(x2-x1) def angle(s1, s2): return math.degrees(math.atan((s2-s1)/(1+(s2*s1)))) lineA = ((0.6, 3.6), (1.6, 3)) lineB = ((1.6, 3), (2, 3.6)) slope1 = slope(lineA[0][0], lineA[0][1], lineA[1][0], lineA[1][1]) slope2 = slope(lineB[0][0], lineB[0][1], lineB[1][0], lineB[1][1]) ang = angle(slope1, slope2) print('Angle in degrees = ', ang) 
+1
source

Source: https://habr.com/ru/post/981828/


All Articles