Source code for angle

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
.. module:: angle.py
  :platform: Unix, Windows
  :synopsis: Ulyxes - an open source project to drive total stations and
      publish observation results.
      GPL v2.0 license
      Copyright (C) 2010- Zoltan Siki <siki.zoltan@epito.bme.hu>

.. moduleauthor:: dr. Zoltan Siki <siki.zoltan@epito.bme.hu>
                Bence Turak <bence.turak@gmail.com>
"""

import math
import re

RO = 180 * 60 * 60 / math.pi
PI2 = 2 * math.pi

def _deg2rad(angle):
    """ Convert DEG to RAD
    """
    return math.radians(angle)

def _gon2rad(angle):
    """ Convert GON to RAD
    """
    return angle / 200.0 * math.pi

def _dms2rad(dms):
    """ Convert DMS to RAD
    """
    if re.search('^[0-9]{1,3}(-[0-9]{1,2}){0,2}$', dms):
        items = [float(item) for item in dms.split('-')]
        div = 1.0
        a = 0.0
        for val in items:
            a += val / div
            div *= 60.0
        a = math.radians(a)
    else:
        raise ValueError("Angle invalid argument", dms)
    return a

def _dm2rad(angle):
    """ Convert DDMM.nnnnnn NMEA angle to radian"
    """
    w = angle / 100.0
    d = int(w)
    return math.radians(d + (w - d) * 100.0 / 60.0)

def _pdeg2rad(angle):
    """ Convert dd.mmss to radian
    """
    d = math.floor(angle)
    angle = round((angle - d) * 100, 10)
    m = math.floor(angle)
    s = round((angle - m) * 100, 10)
    return math.radians(d + m / 60.0 + s / 3600.0)

def _sec2rad(angle):
    """ Convert seconds to radian
    """
    return angle / RO

def _mil2rad(angle):
    """ Convert mills to radian
    """
    return angle / 6400.0 * 2.0 * math.pi

def _rad2gon(value):
    """ Convert radian to GON
    """
    return value / math.pi * 200.0

def _rad2sec(value):
    """ Convert radian to seconds
    """
    return value * RO

def _rad2deg(value):
    """ Convert radian to decimal degrees
    """
    return math.degrees(value)

def _dms(value):
    """ Convert radian to DMS
    """
    signum = "-" if value < 0 else ""
    secs = round(_rad2sec(abs(value)))
    mi, sec = divmod(secs, 60)
    deg, mi = divmod(mi, 60)
    deg = int(deg)
    return f"{signum}{deg}-{mi:02d}-{sec:02d}"

def _rad2dm(value):
    """ Convert radian to NMEA DDDMM.nnnnn
    """
    w = value / math.pi * 180.0
    d = int(w)
    return d * 100 + (w - d) * 60

def _rad2pdeg(value):
    """ Convert radian to pseudo DMS ddd.mmss
    """
    secs = round(_rad2sec(value))
    mi, sec = divmod(secs, 60)
    deg, mi = divmod(mi, 60)
    deg = int(deg)
    return deg + mi / 100.0 + sec / 10000.0

def _rad2mil(value):
    """ Convert radian to mills
    """
    return value / math.pi / 2.0 * 6400.0

[docs]class Angle(): """ Angle class, value stored in radian internally. Angle units supported: * RAD radians (e.g. 1.54678432) * DMS sexagesimal (Degree-Minute-Second, e.g. 123-54-24) * DEG decimal degree (e.g. 25.87659) * GON gradians, the whole circle is 400g (e.g. 387.7857) * NMEA ddmm.mmmm used in NMEA sentences (e.g. 47.338765) * PDEG pseudo sexagesimal (e.g. 156.2745 = 156-27-45) * SEC sexagesimal seconds * MIL mills the whole circle is 6400 mills Operators supported: * \+ add two angles (e.g. c = Angle(180, 'DEG') + Angle('12-34-56', 'DMS')) * \- substract two angles (e.g. d = Angle(180, 'DEG') - Angle('12-34-56', 'DMS')) * += increment angle (e.g. c += Angle(1, 'GON')) * -= decrement angle (e.g. d -= Angle(1, 'GON')) * str() convert angle to GON string, used in print :param value: angle value :param unit: angle unit (available units RAD/DMS/DEG/GON/NMEA/PDEG/SEC/MIL) """ # jump table to import from im = {'DMS': _dms2rad, 'DEG': _deg2rad, 'GON': _gon2rad, 'NMEA': _dm2rad, 'PDEG': _pdeg2rad, 'SEC': _sec2rad, 'MIL': _mil2rad} # jump table for convert to ex = {'DMS': _dms, 'DEG': _rad2deg, 'GON': _rad2gon, 'NMEA': _rad2dm, 'PDEG': _rad2pdeg, 'SEC': _rad2sec, 'MIL': _rad2mil} def __init__(self, value, unit='RAD'): """ Constructor for an angle instance. """ self.value = None self.SetAngle(value, unit)
[docs] def GetAngle(self, out='RAD'): """ Get angle value in different units :param out: output unit (str RAD/DMS/DEG/GON/NMEA/PDEG/SEC/MIL) :returns: value (float or string) """ if out == 'RAD' or self.value is None: output = self.value # no conversion elif out in self.ex: output = self.ex[out](self.value) # call converter based on output format else: output = None # unsupported output format return output
[docs] def SetAngle(self, value, unit='RAD'): """ Set or change value of angle. :param value: new value for angle (str or float) :param unit: unit for the new value (str) """ if unit == 'RAD' or value is None: self.value = value elif unit in self.im: self.value = self.im[unit](value) else: # unknown unit self.value = None # move angle to -2*PI : 2*PI interval if self.value is not None: while self.value >= PI2: self.value -= PI2 while self.value < -PI2: self.value += PI2
[docs] def Positive(self): """ Change stored value to positive """ if self.value < 0: self.value += PI2 return self
[docs] def Normalize(self): """ Normalize angle between 0-360 DEG """ while self.value < 0: self.value += PI2 while self.value >= PI2: self.value -= PI2 return self
def __str__(self): """ GON string representation of angle :returns: GON string """ return f"{self.GetAngle('GON'):.4f}" def __repr__(self): """ angle object representation :returns: angle object string """ return type(self).__name__ + f"({self.GetAngle():f})" def __add__(self, a): """ add angles :param a: Angle to add :returns: sum of the two angles (Angle) """ return Angle(self.value + a.GetAngle('RAD'), 'RAD') def __iadd__(self, a): """ add an angle to current :param a: Angle to add """ self.value += a.GetAngle('RAD') return self def __sub__(self, a): """ substract angles :param a: Angle to substract :returns: difference of the two angles (Angle) """ return Angle(self.value - a.GetAngle('RAD'), 'RAD') def __isub__(self, a): """ substract an agle from current :param a: Angle to substract """ self.value -= a.GetAngle('RAD') return self
if __name__ == "__main__": a = Angle(-0.01112, "DEG") print(a.GetAngle("DMS")) a1 = Angle("204-55-28", "DMS") print(a1.GetAngle('DMS')) a1 += Angle(180, 'DEG') print(a1.Normalize().GetAngle('DMS')) a2 = Angle('280-03-24', 'DMS') print(a2.GetAngle('DMS')) a3 = Angle(360, 'DEG') print(a3.GetAngle('DMS')) a2 = a3 - a2 print(a2.Normalize().GetAngle('DMS')) for u in ['RAD', 'DMS', 'GON', 'NMEA', 'DEG', 'PDEG', 'MIL']: print(a1.GetAngle(u)) b1 = Angle(1.1111, 'PDEG') print(b1.GetAngle("DMS")) c1 = a1 + b1 print(c1.GetAngle("DMS")) print(c1) print((a1-b1).GetAngle("DMS")) a2 = Angle(-90, 'DEG') a2.Positive() print(a2)