"""point.py - Represents a generic point on a sphere as a Python object.

   See documentation of class Point for details.
   Ole Nielsen, ANU 2002
"""


from math import cos, sin, pi

def acos(c):
  """acos -  Safe inverse cosine
  
     Input argument c is shrunk to admissible interval
     to avoid case where a small rounding error causes
     a math domain error.
  """
  from math import acos

  if c > 1: c=1
  if c < -1: c=-1

  return acos(c)




class Point:
    """Definition of a generic point on the sphere.
  
    Defines a point in terms of latitude and longitude
    and computes distances to other points on the sphere.

    Initialise as
      Point(lat, lon), where lat and lon are in decimal degrees (dd.dddd)
      

    Public Methods: 
        distance_to(P)
        bearing_to(P)
        dist(P)

    Author: Ole Nielsen, ANU 2002        
    """
     
    # class constants
    R = 6372000  # Approximate radius of Earth (m)
    degrees2radians = pi/180.0
    

    def __init__(self, latitude, longitude):

        assert(latitude >= -90 and latitude <= 90.0)
        assert(longitude >= -180 and longitude <= 180.0)

        self.latitude =  float(latitude)
        self.longitude = float(longitude) 

        lat = latitude * self.degrees2radians    # Converted to radians
        lon = longitude * self.degrees2radians   # Converted to radians
        self.coslat = cos(lat)
        self.coslon = cos(lon)
        self.sinlat = sin(lat)
        self.sinlon = sin(lon)

    def BearingTo(self,P):
        """ Bearing (in degrees) to point P"""
        AZ = self.AZ(P)
        return int(round(AZ/self.degrees2radians))

    
    def DistanceTo(self,P):
        """ Distance to point P"""           
        GCA = self.GCA(P)
        return self.R*GCA


    def Dist(self,P):
        """ Very cheap and rough approximation to distance"""
        return max(abs(self.latitude-P.latitude),abs(self.longitude-P.longitude))


    #--------------------------------------------------------------------------

    def __repr__(self):
        d = 2
        lat = round(self.latitude,d)
        lon = round(self.longitude,d)
        return ' (' + str(lat)+ ', '+ str(lon)+')'


    def GCA(self,P):
        """ Compute the Creat Circle Angle (GCA) between current point and P.
        """
        
        alpha = P.coslon*self.coslon + P.sinlon*self.sinlon
        # The original formula is alpha = cos(self.lon - P.lon)
        # but rewriting lets us make us of precomputed trigonometric values.
        
        x = alpha*self.coslat*P.coslat + self.sinlat*P.sinlat
        return acos(x)

    
    def AZ(self,P):
        """ Compute Azimuth bearing (AZ) from current point to P"""

        # Compute cosine(AZ), where AZ is the azimuth angle
        GCA = self.GCA(P)
        c = P.sinlat - self.sinlat*cos(GCA)
        c = c/self.coslat/sin(GCA)

        AZ = acos(c)

        # Reverse direction if bearing is westward, i.e. sin(self.lon - P.lon) > 0
        # Without this correction the bearing due west, say, will be 90 degrees
        # because the formulas work in the positive direction which is east.
        #
        # Precomputed trigonometric values are used to rewrite the formula:

        if self.sinlon*P.coslon - self.coslon*P.sinlon > 0: AZ = 2*pi - AZ
    
        return AZ



#-----------------------------------------------------------------------

if __name__ == "__main__":
    pass
