3 Points to 2D Angle

#!/usr/bin/python3
# ====================================================================
# given three (2D) points, calculate the 2D angle formed by them
# =====================================================================

import sys
import numpy as np
import user_interface as ui


interactive_description = r'''
------------------------------------------------------------
In interactive mode, the origin (0,0,0) is asummed to be
the middle point used in the calculation (point B).

            A
           / 
          /
         B
          \ 
           \
            C

Points A are C entered by the user and the angle calculated.

The smaller angle is returned - no angles over 180 degrees is returned

if an error occurs 0.0 is returned
------------------------------------------------------------
'''
 
# --------------------------------------------------------------------
# ---- get 2D angle in degrees from three points a,b,c
# --------------------------------------------------------------------

def get_2d_angle(a, b, c):

    try:
        v0 = np.array(a) - np.array(b)
        v1 = np.array(c) - np.array(b)
        ang = np.math.atan2(np.linalg.det([v0,v1]),np.dot(v0,v1))
        ang = np.degrees(ang)
    except:
        ang  = 0.0

    return abs(ang)

 
# --------------------------------------------------------------------
# ---- pre-defined angle tests
# --------------------------------------------------------------------

def predefined_angle_tests():

    tests_2d = [
       [ 'counter clockwise (positive angles)'],
       [ (5,0),  (0,0),(5,0),    'g. Test 0   degrees' ],
       [ (5,-5), (0,0),(5,0),    'a. Test 45  degrees' ],
       [ (0,-5), (0,0),(5,0),    'b. Test 90  degrees' ],
       [ (-5,-5),(0,0),(5,0),    'c. Test 135 degrees' ],
       [ (-5,0), (0,0),(5,0),    'd. Test 180 degrees' ],
       [ (-5,5), (0,0),(5,0),    'e. Test 225 degrees' ],
       [ (0,5),  (0,0),(5,0),    'f. Test 270 degrees' ],
       [ (5,5),  (0,0),(5,0),    'g. Test 315 degrees' ],
       [ (5,0),  (0,0),(5,0),    'g. Test 360 degrees' ],

       [ 'special case tests - illegal points/angles' ],        
       [ (0,0),(0,0),(0,0),
         'b. Test (0,0,0)                   point' ],
       [ (5,5),(5,5),(5,5),
         'c. Test (5,5,0)                   point' ],

       [ 'clockwise (negtave angles)'],
       [ (5,0),(0,0),(5,-5),     'a. Test -45   degrees' ],
       [ (5,0),(0,0),(0,-5),     'b. Test -90   degrees' ],
       [ (5,0),(0,0),(-5,-5),    'c. Test -135  degrees' ],
       [ (5,0),(0,0),(-5,0),     'd. Test -180  degrees' ],
       [ (5,0),(0,0),(-5,5),     'e. Test -225  degrees' ],
       [ (5,0),(0,0),(0,5),      'f. Test -270  degrees' ],
       [ (5,0),(0,0),(5,5),      'g. Test -315  degrees' ],
               ]

    print('----------------------------------------------------')
    print('test various angles and rotations')
    print(' (vector a to vector b) and (vector b to vector a)')
    print('----------------------------------------------------')

    for t  in tests_2d:

        if len(t) < 4:
            print()
            for tt in t:
                print(f'---- {tt}')
            continue

        print()
        print(f'---- {t[3]}')
        
        ang,aang = get_2d_angle(t[0],t[1],t[2])
        
        print(f'returned ang = {ang:<7.2f}  ' +
              f'aang = {aang:<7.2f} degrees')


# --------------------------------------------------------------------
# ---- ask the user for a point's coordinates
# --------------------------------------------------------------------

def get_point_coordinates(title):

    print()
    print(title)

    while True:

        print()
        s = ui.get_user_input('Enter x,y coordinates: ')

        if not s: sys.exit()
        
        s = s.replace(',', ' ')
        s = s.replace(r'/', ' ')
        s = s.replace(':', ' ')
        sx,sy = s.split()
        
        tf,x = ui.is_float(sx)
        if not tf:
            print('bad X coord entered')
            continue

        tf,y = ui.is_float(sy)
        if not tf:
            print('bad y coord entered')
            continue

        return (x,y)
    
# --------------------------------------------------------------------
# ---- interactive angle calculation tests
# --------------------------------------------------------------------

def interactive_angle_tests(center_point):

    print(interactive_description)

    while True:

        a = get_point_coordinates(
            'Enter x,y coordinates for point A')

        c = get_point_coordinates(
            'Enter x,y coordinates for point C')

        ang = get_2d_angle(a,center_point,c)

        print()
        print(f'Angle is {ang:.4} degrees')

        ##ui.pause()

# --------------------------------------------------------------------
# ---- main
# --------------------------------------------------------------------

if __name__ == '__main__':

    # ---- pre defined lat/lon test
    
    ##predefined_angle_tests()

    # ---- interactive lat/lon test
    
    interactive_angle_tests((0,0))