import math import doctest from .angles import Angle # ======================================================== # ======================================================== # === COORD # ======================================================== # ======================================================== class Coords: """ Class to convert coordinates for astronomy Coordinate formats are: * cart = tuple of x, xy, xyz * sphe = tuple of r, rp, rpt :Usage: First, instanciate an object from the class: :: coord = Coords() Second, assign a coordinate in any format: :: xyz = (x, y, z) coord.coords(xyz) or :: phi = Angle(45) theta = Angle(120) rpt = (r, phi, theta) coord.coords(rpt) Third, get the converted angles: :: r, phi, theta = coord.sphe() x,y,y = coord.cart() Fourth, some tools :: coord.separation(coord2) coord.pole(coord2) coord.scalar_product(coord2) coord.vectorial_product(coord2) :Informations: :: help(Coords) Coords().infos("doctest") Coords().infos("doc_methods") """ # ======================================================== # === attributs # ======================================================== _TYPE_UNKN = 0 _TYPE_CART = 1 _TYPE_SPHE = 2 _X = 0 _Y = 1 _Z = 2 _R = 0 _PHI = 1 _THETA = 2 # --- _x = 0 _y = 0 _z = 0 _r = 0 _phi = 0 _theta = 0 _cos_phi = 1 _sin_phi = 0 _cos_theta = 1 _sin_theta = 0 _computed_rpt = 0 _computed_xyz = 0 _init_cart_or_sphe = (0, 0, 0) _init_type = _TYPE_UNKN # ======================================================== # === internal methods # ======================================================== def _coord_cart_or_sphe_decode(self,cart_or_sphe): """ Decode the cart_or_sphe tuple into three floats and the type. """ dim = len(cart_or_sphe) # --- Verify integrity of the input tuple if (dim>=1): p1 = cart_or_sphe[0] if isinstance(p1, (int, float)) == False: raise TypeError return "" if (dim>=2): p2 = cart_or_sphe[1] if isinstance(p2, (int, float, Angle)) == False: raise TypeError return "" if (dim>=3): p3 = cart_or_sphe[2] if isinstance(p3, (int, float, Angle)) == False: raise TypeError return "" else: raise TypeError return "" # --- x = 0 y = 0 z = 0 r = 0 phi = 0 theta = 0 if (dim==1): init_type =self. _TYPE_CART x = cart_or_sphe[self._X] if (dim>=2): p1 = cart_or_sphe[0] p2 = cart_or_sphe[1] if isinstance(p2, (int, float)) == True: init_type = self._TYPE_CART x = cart_or_sphe[self._X] y = cart_or_sphe[self._Y] else: init_type = self._TYPE_SPHE if isinstance(p2, Angle) == False: p2 = Angle(p2) r = cart_or_sphe[self._R] phi = p2 if (dim==3): p3 = cart_or_sphe[2] if (isinstance(p2, (int, float)) == True) and (isinstance(p3, (int, float)) == True): init_type = self._TYPE_CART x = cart_or_sphe[self._X] y = cart_or_sphe[self._Y] z = cart_or_sphe[self._Z] else: init_type =self. _TYPE_SPHE if isinstance(p2, Angle) == False: p2 = Angle(p2) if isinstance(p3, Angle) == False: p3 = Angle(p3) r = cart_or_sphe[self._R] phi = p2 theta = p3 ppp = (0,0,0) if init_type == self._TYPE_CART: ppp = (x, y, z) elif init_type == self._TYPE_SPHE: ppp = (r, phi.rad(), theta.rad()) return init_type, ppp def _init(self,cart_or_sphe=(0,0,0)): """ Initialize internal attributes. :param cart_or_sphe: Cartesian or spherical coordinates. If the tupple contains two elements it is spherical coordinates. If the tupple contains three elements it is cartesian coordinates. :type cart_or_sphe: tuple :Example: objangle = Coords() objangle._init() """ self._computed_rpt = 0 self._computed_xyz = 0 self._init_dim = 0 self._init_type = self._TYPE_UNKN init_type, ppp = self._coord_cart_or_sphe_decode(cart_or_sphe) # print("init_type={} ppp={} ".format(init_type, ppp )) # --- if init_type == self._TYPE_UNKN: raise Exception return "" self._init_type = init_type self._cos_phi = 1 self._sin_phi = 0 self._cos_theta = 1 self._sin_theta = 0 if self._init_type == self._TYPE_CART: self._x = ppp[self._X] self._y = ppp[self._Y] self._z = ppp[self._Z] xyz = (self._x, self._y, self._z) res = self.coord_xyz2rpt(xyz) self._r, self._phi, self._theta, self._cos_phi, self._sin_phi, self._cos_theta, self._sin_theta = res else: self._r = ppp[self._R] self._phi = ppp[self._PHI] self._theta = ppp[self._THETA] rpt = (self._r, self._phi, self._theta) res = self.coord_rpt2xyz(rpt) self._x, self._y, self._z, self._cos_phi, self._sin_phi, self._cos_theta, self._sin_theta = res self._computed_rpt = 1 self._computed_xyz = 1 self._init_cart_or_sphe = cart_or_sphe def _coord_compare(self, coords, operator): """ Comparaison of radius coords for various operators. :param coords: An coords in any supported format (cf. help(Coords)) :type coords: Coords() :param operator: Operator such as == != > >= < <= :type operator: string :returns: The logic result of the comparison. :rtype: bool :Example: >>> objcoord1 = Coords() >>> objcoord2 = Coords() >>> objcoord1.coords((1,2,3)) ; objcoord2.coords((4,5,6)) ; objcoord1._coord_compare(objcoord2,">") (1, 2, 3) (4, 5, 6) False """ if isinstance(coords, Coords) == False: coords = Coords(coords) res = False if (self._computed_rpt == 1) and (coords._computed_rpt == 1): toeval = str(self._r)+" "+operator+" "+str(coords._r) res = eval(toeval) return res # ======================================================== # === coords methods # ======================================================== def coord_xyz2rpt(self, xyz): if (self._computed_rpt ==0): x, y, z = xyz r = math.sqrt(x*x + y*y + z*z) phi = math.atan2(y,x) if (r==0): theta = 0 else: theta = math.asin(z/r) cos_theta = math.cos(theta) sin_theta = math.sin(theta) cos_phi = math.cos(phi) sin_phi = math.sin(phi) return r, phi, theta, cos_phi, sin_phi, cos_theta, sin_theta else: return self._r, self._phi, self._theta, self._cos_phi, self._sin_phi, self._cos_theta, self._sin_theta def coord_rpt2xyz(self, rpt): if (self._computed_xyz ==0): r, phi, theta = rpt cos_theta = math.cos(theta) sin_theta = math.sin(theta) cos_phi = math.cos(phi) sin_phi = math.sin(phi) d = r * cos_theta x = d * cos_phi y = d * sin_phi z = r * sin_theta return x, y, z, cos_phi, sin_phi, cos_theta, sin_theta else: return self._x, self._y, self._z, self._cos_phi, self._sin_phi, self._cos_theta, self._sin_theta def great_circle_distance(self, coords): if isinstance(coords, Coords) == False: coords = Coords(coords) dx = coords._cos_theta * coords._cos_phi - self._cos_theta * self._cos_phi dy = coords._cos_theta * coords._sin_phi - self._cos_theta * self._sin_phi dz = coords._sin_theta - self._sin_theta #print("dd={} dz={} ct1={} ct2={}".format(math.sqrt(dx*dx + dy*dy), dz,coords._cos_theta,self._cos_theta)) c = math.sqrt(dx*dx + dy*dy + dz*dz) d = 2*math.asin(c/2) return d * Angle()._r2d # ======================================================== # === get/set methods # ======================================================== def coords(self, cart_or_sphe): """ Set the input cart_or_sphe in any format :param cart_or_sphe: Cartesian or spherical coordinates. If the tupple contains two elements it is spherical coordinates. If the tupple contains three elements it is cartesian coordinates. :type cart_or_sphe: tuple Angles are in any supported format (cf. help(Coords)) :Example: >>> objcoord = Coords() >>> objcoord.coords((1,3,6)) (1, 3, 6) :Related topics: After using objcoord.coords() get conversions with methods as objcoord.cart() or objcoord.sphe(). """ if cart_or_sphe != "": if (cart_or_sphe != self._init_cart_or_sphe): self._init(cart_or_sphe) self.init_cart_or_sphe = cart_or_sphe return self.init_cart_or_sphe def cart(self): """ Get the cartesian coordinates :returns: The tuple (x,y,z) :rtype: tuple(float, float, float) :Example: >>> objcoord = Coords() >>> objcoord.coords((1,3,6)) (1, 3, 6) >>> objcoord.cart() (1, 3, 6) .. note:: Before use objcoord.cart() to set the input coords with objcoord.coords(). """ if (self._computed_xyz == 0): raise Exception return "" return self._x, self._y, self._z def sphe(self,format_phi="deg",format_theta="deg"): """ Get the spherical coordinates :param format_phi: Angle format unit. :type format_phi: str :param format_theta: Angle format unit. :type format_theta: str :returns: The tuple (x,y,z) :rtype: tuple(float, float, float) :Example: >>> objcoord = Coords() >>> objcoord.coords((1,3,6)) (1, 3, 6) >>> objcoord.sphe("H","d") (6.782329983125268, '4h46m15.61s', '62d12m31.30s') .. note:: Before use objcoord.sphe() to set the input coords with objcoord.coords(). """ if (self._computed_rpt == 0): raise Exception return "" if format_phi=="rad": pout = self._phi elif format_phi=="deg": pout = self._phi * Angle()._r2d else: p = Angle(self._phi * Angle()._r2d) pout = p.sexagesimal(format_phi) if format_theta=="rad": tout = self._theta elif format_theta=="deg": tout = self._theta * Angle()._r2d else: t = Angle(self._theta * Angle()._r2d) tout = t.sexagesimal(format_theta) return self._r, pout, tout # ======================================================== # === debug methods # ======================================================== def infos(self, action) -> None: """ To get informations about this class :param action: A command to run a debug action (see examples). :type action: string :Example: Coords().infos("doctest") Coords().infos("doc_methods") Coords().infos("internal_attributes") Coords().infos("public_methods") """ if (action == "doc_methods"): publics = [x for x in dir(self) if x[0]!="_"] for public in publics: varname = "{}".format(public) if (callable(getattr(self,varname))==True): print("\n{:=^40}".format(" method "+varname+" ")) t = "Coords()."+varname+".__doc__" tt =eval(t) print(tt) if (action == "doctest"): if __name__ == "__main__": print("\n{:~^40}".format("doctest")) doctest.testmod(verbose=False) if (action == "internal_attributes"): internals = [x for x in dir(self) if x[0]=="_" and x[1]!="_"] for internal in internals: varname = "{}".format(internal) #if (hasattr(self,varname)==True): if (callable(getattr(self,varname))==False): print(varname + "=" + str(getattr(self,varname))) if (action == "public_methods"): publics = [x for x in dir(self) if x[0]!="_"] for public in publics: varname = "{}".format(public) if (callable(getattr(self,varname))==True): print(varname) # ======================================================== # === special methods # ======================================================== def __init__(self, cart_or_sphe=(0,0,0)): """ Object initialization where cart_or_sphe is the input tuple of cartesian or spheric coordinates. :param cart_or_sphe: An angle in any supported format (cf. help(Angle)) :type cart_or_sphe: tuple cartesian coords = tuple(float, float, float) spheric coords = tuple(float, Angle, Angle) """ self._init(cart_or_sphe)