title: Signed angle between directed line segments
body: <p>I have an algorithm in which I need to work out the signed angle (-180 to 180) between edges in a graph. I've done some research and found plenty of specific answers but can't figure out how to relate them to my situation (e.g. <a href="https://stackoverflow.com/questions/28260962/calculating-angles-between-line-segments-python-with-math-atan2">this</a>  question which uses <code>atan2</code>, however the OP wanted only positive angles)
  I've tried implementing a few different ways (using atan2 or arccos) but I'm struggling to relate the examples to my specific problem. I've tried treating the edges as vectors but got strange results.</p>
  
  <p>Given a graph with points (A, B, C, D, E), and the average of those points (avg)... how do I find the signed angle between one of those points (e.g. A) and the other points (e.g. B, C, D, E), taking the angle from the current origin (A) to the 'avg' point as equal to 0 degrees. Example below...</p>
  
  <p><a href="https://i.stack.imgur.com/Hzpyj.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/Hzpyj.png" alt="enter image description here"></a></p>
  
  <p>...in this example, the anti-clockwise angle from (A, avg) to (A, B) would be positive something (between 0 and 180), and the angle from (A, avg) to (A, E) would be negative something (between 0 and -180).</p>
  
  <p>Ideally I want a formula which I could also apply to defining any of the points as the origin, for example taking point C as the origin.. the 'zero angle' would be (C, avg) and the angle between (C, avg) and (C, A) would be negative (0 to -180) and the angle between (C, avg) and (C, E) would be positive (0 to 180).</p>
  
  <p>I haven't studied math beyond high-school so I find it hard to decipher equations with symbols I don't understand.</p>
  
  <p>UPDATE: Thought I'd clean this up to make it more obvious what the conclusion was.
  I made two small changes to the accepted answer, resulting in the below snippet:</p>
  
  <pre><code>def angle(vertex, start, dest):
      AhAB = math.atan2((dest.y - vertex.y), (dest.x - vertex.x))
      AhAO = math.atan2((start.y - vertex.y), (start.x - vertex.x))
      AB = AhAB - AhAO
      # in between 0-math.pi = do nothing, more than math.pi = +(-2 * math.pi), less than zero = do nothing
      AB = math.degrees(AB + (-2 * math.pi if AB &gt; math.pi else (2 * math.pi if AB &lt; 0 - math.pi else 0)))
      return AB
  </code></pre>
  
  <p>...the final one-liner may be a bit much to grok after a few months of not working on this, so I turned it into it's own function, taking the result of <code>AB = AhAB - AhAO</code> as it's argument...</p>
  
  <pre><code>def calc(ab):
      if ab &gt; math.pi:
          return ab + (-2 * math.pi)
      else:
          if ab &lt; 0 - math.pi:
              return ab + (2 * math.pi)
          else:
              return ab + 0
  </code></pre>
  
  <p>I thought this was a little clearer to read, though more lines.</p>
  
  <p>The final function in full:</p>
  
  <pre><code>def angle(vertex, start, dest):
      """Calculates the signed angle between two edges with the same origin. 
         Origin is the 'vertex' argument, 'start' is the bounding point of the edge to calculate the angle from.
         Positively signed result means anti-clockwise rotation about the vertex."""
  
      def calc_radians(ab):
          if ab &gt; math.pi:
              return ab + (-2 * math.pi)
          else:
              if ab &lt; 0 - math.pi:
                  return ab + (2 * math.pi)
              else:
                  return ab + 0
  
      AhAB = math.atan2((dest.y - vertex.y), (dest.x - vertex.x))
      AhAO = math.atan2((start.y - vertex.y), (start.x - vertex.x))
  
      res = calc_radians(AhAB - AhAO)
  
      return math.degrees(res)
  </code></pre>
  
  <p><strong>Note</strong>: The function assumes the three arguments will all be instances of a typical <code>Point</code> class with <code>x</code> and <code>y</code> attributes.
  Also, the example graph above has only positive values, but I am fairly sure that this works with graphs that involve negative values too.</p>