diff --git a/geodepy/angles.py b/geodepy/angles.py new file mode 100644 index 0000000..dbb6ffe --- /dev/null +++ b/geodepy/angles.py @@ -0,0 +1,1220 @@ +#!/usr/bin/env python3 + +""" +Geoscience Australia - Python Geodesy Package +Angles Module + +GeodePy supports Angular Notation in 9 different formats + +ABRV - FORMAT (type) +-------------------- +rad - Radians (stored as float) +dec - Decimal Degrees (stored as float) +deca - Decimal Degrees (via DECAngle class) +hp - Hewlett Packard (HP) Notation (stored as float) +hpa - Hewlett Packard (HP) Notation (via HPAngle class) +gon - Gradians (stored as float) +gona - Gradians (via GONAngle class) +dms - Degrees, Minutes and Seconds Notation (via DMSAngle class) +ddm - Degrees and Decimal Minutes Notation (via DDMAngle class) + +Conversion between all formats is supported as shown below: + +Radians to/from Decimal Degrees via builtin math.radians and math.degrees + +Formats as floats to all other types via functions in the form abrv2abrv +e.g. gon2hpa() + +DECAngle, HPAngle, GONAngle, DMSAngle and DDMAngle class objects via methods in +the form CLASS.abrv() +e.g. HPAngle(value).dec() + +""" + +from math import radians + + +class DECAngle(float): + """ + Class for working with angles in Decimal Degrees + Note: GeodePy also supports working with angles in Decimal Degrees as floats + """ + + def __init__(self, dec_angle=0.0): + """ + :param dec_angle: float Decimal Degrees angle + """ + super().__init__() + self.dec_angle = float(dec_angle) + + def __repr__(self): + if self.dec_angle >= 0: + return '{DECAngle: +' + str(self.dec_angle) + '}' + else: # negative + return '{DECAngle: ' + str(self.dec_angle) + '}' + + def __add__(self, other): + try: + return DECAngle(self.dec() + other.dec()) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __radd__(self, other): + try: + return DECAngle(other.dec() + self.dec()) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __sub__(self, other): + try: + return DECAngle(self.dec() - other.dec()) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __rsub__(self, other): + try: + return DECAngle(other.dec() - self.dec()) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __mul__(self, other): + try: + return DECAngle(self.dec() * other) + except TypeError: + raise TypeError('Multiply only defined between DECAngle Object ' + 'and Int or Float') + + def __rmul__(self, other): + try: + return DECAngle(other * self.dec()) + except TypeError: + raise TypeError('Multiply only defined between DECAngle Object ' + 'and Int or Float') + + def __truediv__(self, other): + try: + return DECAngle(self.dec() / other) + except TypeError: + raise TypeError('Division only defined between DECAngle Object ' + 'and Int or Float') + + def __abs__(self): + return DECAngle(abs(self.dec_angle)) + + def __neg__(self): + return DECAngle(-self.dec()) + + def __eq__(self, other): + return self.dec() == other.dec() + + def __ne__(self, other): + return self.dec() != other.dec() + + def __lt__(self, other): + return self.dec() < other.dec() + + def __gt__(self, other): + return self.dec() > other.dec() + + def __int__(self): + return int(self.dec_angle) + + def __float__(self): + return float(self.dec_angle) + + def __str__(self): + return str(self.dec_angle) + + def __round__(self, n=None): + return DECAngle(round(self.dec_angle, n)) + + def rad(self): + """ + Convert to radians + :return: radians + :rtype: float + """ + return radians(self.dec_angle) + + def dec(self): + """ + Convert to Decimal Degrees (float) + :return: Decimal Degrees + :rtype: float + """ + return self.dec_angle + + def hp(self): + """ + Convert to HP Notation + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + return dec2hp(self.dec_angle) + + def hpa(self): + """ + Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) + :rtype: HPAngle + """ + return HPAngle(self.hp()) + + def gon(self): + """ + Convert to Gradians (float) + :return: Gradians + :rtype: float + """ + return dec2gon(self.dec_angle) + + def gona(self): + """ + Convert to Gradians (class) + :return: Gradians + :rtype: GONAngle + """ + return GONAngle(dec2gon(self.dec_angle)) + + def dms(self): + """ + Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + return dec2dms(self.dec_angle) + + def ddm(self): + """ + Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + return dec2ddm(self.dec_angle) + + +class HPAngle(object): + """ + Class for working with angles in Hewlett-Packard (HP) format + Note: GeodePy also supports working with angles in HP format as floats + """ + def __init__(self, hp_angle=0.0): + """ + :param hp_angle: float HP angle + """ + self.hp_angle = float(hp_angle) + hp_dec_str = f'{self.hp_angle:.17f}'.split('.')[1] + if int(hp_dec_str[0]) > 5: + raise ValueError(f'Invalid HP Notation: 1st decimal place greater ' + f'than 5: {self.hp_angle}') + if len(hp_dec_str) > 2: + if int(hp_dec_str[2]) > 5: + raise ValueError( + f'Invalid HP Notation: 3st decimal place greater ' + f'than 5: {self.hp_angle}') + + def __repr__(self): + if self.hp_angle >= 0: + return '{HPAngle: +' + str(self.hp_angle) + '}' + else: # negative + return '{HPAngle: ' + str(self.hp_angle) + '}' + + def __add__(self, other): + try: + return HPAngle(dec2hp(self.dec() + other.dec())) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __radd__(self, other): + try: + return HPAngle(dec2hp(other.dec() + self.dec())) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __sub__(self, other): + try: + return HPAngle(dec2hp(self.dec() - other.dec())) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __rsub__(self, other): + try: + return HPAngle(dec2hp(other.dec() - self.dec())) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __mul__(self, other): + try: + return HPAngle(dec2hp(self.dec() * other)) + except TypeError: + raise TypeError('Multiply only defined between Angle objects and ' + 'Int or Float') + + def __rmul__(self, other): + try: + return HPAngle(dec2hp(other * self.dec())) + except TypeError: + raise TypeError('Multiply only defined between Angle objects and ' + 'Int or Float') + + def __truediv__(self, other): + try: + return HPAngle(dec2hp(self.dec() / other)) + except TypeError: + raise TypeError('Division only defined between HPAngle objects ' + 'and Int or Float') + + def __abs__(self): + return HPAngle(abs(self.hp_angle)) + + def __neg__(self): + return HPAngle(self.hp_angle.__neg__()) + + def __eq__(self, other): + return self.dec() == other.dec() + + def __ne__(self, other): + return self.dec() != other.dec() + + def __lt__(self, other): + return self.dec() < other.dec() + + def __gt__(self, other): + return self.dec() > other.dec() + + def __int__(self): + return int(self.hp_angle) + + def __float__(self): + return float(self.hp_angle) + + def __str__(self): + return str(self.hp_angle) + + def __round__(self, n=None): + return HPAngle(round(self.hp_angle, n)) + + def rad(self): + """ + Convert to Radians + :return: Radians + :rtype: float + """ + return radians(hp2dec(self.hp_angle)) + + def dec(self): + """ + Convert to Decimal Degrees (float) + :return: Decimal Degrees + :rtype: float + """ + return hp2dec(self.hp_angle) + + def deca(self): + """ + Convert to Decimal Degrees (class) + :return: Decimal Degrees + :rtype: DECAngle + """ + return DECAngle(self.dec()) + + def hp(self): + """ + Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + return float(self.hp_angle) + + def gon(self): + """ + Convert to Gradians (float) + :return: Gradians + :rtype: float + """ + return hp2gon(self.hp_angle) + + def gona(self): + """ + Convert to Gradians (class) + :return: Gradians + :rtype: GONAngle + """ + return GONAngle(self.gon()) + + def dms(self): + """ + Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + return hp2dms(self.hp_angle) + + def ddm(self): + """ + Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + return hp2ddm(self.hp_angle) + + +class GONAngle(object): + """ + Class for working with angles in Gradians (90 degrees == 100 Gradians) + Note: GeodePy also supports working with angles in Gradians as floats + """ + def __init__(self, gon_angle=0.0): + """ + :param gon_angle: float Gradian angle + """ + super().__init__() + self.gon_angle = float(gon_angle) + + def __repr__(self): + if self.gon_angle >= 0: + return '{GONAngle: +' + str(self.gon_angle) + '}' + else: # negative + return '{GONAngle: ' + str(self.gon_angle) + '}' + + def __add__(self, other): + try: + return GONAngle(dec2gon(self.dec() + other.dec())) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __radd__(self, other): + try: + return GONAngle(dec2gon(other.dec() + self.dec())) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __sub__(self, other): + try: + return GONAngle(dec2gon(self.dec() - other.dec())) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __rsub__(self, other): + try: + return GONAngle(dec2gon(other.dec() - self.dec())) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __mul__(self, other): + try: + return GONAngle(dec2gon(self.dec() * other)) + except TypeError: + raise TypeError('Multiply only defined between Angle objects and ' + 'Int or Float') + + def __rmul__(self, other): + try: + return GONAngle(dec2gon(other * self.dec())) + except TypeError: + raise TypeError('Multiply only defined between Angle objects and ' + 'Int or Float') + + def __truediv__(self, other): + try: + return GONAngle(dec2gon(self.dec() / other)) + except TypeError: + raise TypeError('Division only defined between HPAngle objects ' + 'and Int or Float') + + def __abs__(self): + return GONAngle(abs(self.gon_angle)) + + def __neg__(self): + return GONAngle(self.gon_angle.__neg__()) + + def __eq__(self, other): + return self.dec() == other.dec() + + def __ne__(self, other): + return self.dec() != other.dec() + + def __lt__(self, other): + return self.dec() < other.dec() + + def __gt__(self, other): + return self.dec() > other.dec() + + def __int__(self): + return int(self.gon_angle) + + def __float__(self): + return float(self.gon_angle) + + def __str__(self): + return str(self.gon_angle) + + def __round__(self, n=None): + return GONAngle(round(self.gon_angle, n)) + + def rad(self): + """ + Convert to Radians + :return: Radians + :rtype: float + """ + return radians(gon2dec(self.gon_angle)) + + def dec(self): + """ + Convert to Decimal Degrees (float) + :return: Decimal Degrees + :rtype: float + """ + return gon2dec(self.gon_angle) + + def deca(self): + """ + Convert to Decimal Degrees (class) + :return: Decimal Degrees + :rtype: DECAngle + """ + return DECAngle(self.dec()) + + def hp(self): + """ + Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + return gon2hp(self.gon_angle) + + def hpa(self): + """ + Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) + :rtype: HPAngle + """ + return HPAngle(gon2hp(self.gon_angle)) + + def gon(self): + """ + Convert to Gradians (float) + :return: Gradians + :rtype: float + """ + return float(self.gon_angle) + + def dms(self): + """ + Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + return gon2dms(self.gon_angle) + + def ddm(self): + """ + Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + return gon2ddm(self.gon_angle) + + +class DMSAngle(object): + """ + Class for working with angles in Degrees, Minutes and Seconds format + """ + def __init__(self, degree, minute=0, second=0.0): + """ + :param degree: Angle: whole degrees component (floats truncated) + Alt: formatted string '±DDD MM SS.SSS' + :param minute: Angle: whole minutes component (floats truncated) + :param second: Angle: seconds component (floats preserved) + """ + # Convert formatted string 'DDD MM SS.SSS' to DMSAngle + if type(degree) == str: + str_pts = degree.split(' ') + degree = int(str_pts[0]) + minute = int(str_pts[1]) + second = float(str_pts[2]) + # Set sign of object based on sign of any variable + if degree == 0: + if str(degree)[0] == '-': + self.positive = False + elif minute < 0: + self.positive = False + elif second < 0: + self.positive = False + else: + self.positive = True + elif degree > 0: + self.positive = True + else: # degree < 0 + self.positive = False + self.degree = abs(int(degree)) + self.minute = abs(int(minute)) + self.second = abs(second) + + def __repr__(self): + if self.positive: + signsymbol = '+' + else: + signsymbol = '-' + return '{DMSAngle: ' + signsymbol + str(self.degree) + 'd ' +\ + str(self.minute) + 'm ' + str(self.second) + 's}' + + def __add__(self, other): + try: + return dec2dms(self.dec() + other.dec()) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __radd__(self, other): + try: + return dec2dms(other.dec() + self.dec()) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __sub__(self, other): + try: + return dec2dms(self.dec() - other.dec()) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __rsub__(self, other): + try: + return dec2dms(other.dec() - self.dec()) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __mul__(self, other): + try: + return dec2dms(self.dec() * other) + except TypeError: + raise TypeError('Multiply only defined between DMSAngle Object ' + 'and Int or Float') + + def __rmul__(self, other): + try: + return dec2dms(other * self.dec()) + except TypeError: + raise TypeError('Multiply only defined between DMSAngle Object ' + 'and Int or Float') + + def __truediv__(self, other): + try: + return dec2dms(self.dec() / other) + except TypeError: + raise TypeError('Division only defined between DMSAngle Object ' + 'and Int or Float') + + def __abs__(self): + return DMSAngle(self.degree, self.minute, self.second) + + def __neg__(self): + if self.positive: + return DMSAngle(-self.degree, -self.minute, -self.second) + else: # positive == False + return DMSAngle(self.degree, self.minute, self.second) + + def __eq__(self, other): + return self.dec() == other.dec() + + def __ne__(self, other): + return self.dec() != other.dec() + + def __lt__(self, other): + return self.dec() < other.dec() + + def __gt__(self, other): + return self.dec() > other.dec() + + def __str__(self): + if self.positive: + return (str(self.degree) + ' ' + str(self.minute) + ' ' + + str(self.second)) + else: + return ('-' + str(self.degree) + ' ' + str(self.minute) + ' ' + + str(self.second)) + + def __round__(self, n=None): + if self.positive: + return DMSAngle(self.degree, self.minute, round(self.second, n)) + else: + return -DMSAngle(self.degree, self.minute, round(self.second, n)) + + def rad(self): + """ + Convert to Radians + :return: Radians + :rtype: float + """ + return radians(self.dec()) + + def dec(self): + """ + Convert to Decimal Degrees (float) + :return: Decimal Degrees + :rtype: float + """ + if self.positive: + return self.degree + (self.minute / 60) + (self.second / 3600) + else: + return -(self.degree + (self.minute / 60) + (self.second / 3600)) + + def deca(self): + """ + Convert to Decimal Degrees (class) + :return: Decimal Degrees + :rtype: DECAngle + """ + return DECAngle(self.dec()) + + def hp(self): + """ + Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + if self.positive: + return self.degree + (self.minute / 100) + (self.second / 10000) + else: + return -(self.degree + (self.minute / 100) + (self.second / 10000)) + + def hpa(self): + """ + Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) + :rtype: HPAngle + """ + return HPAngle(self.hp()) + + def gon(self): + """ + Convert to Gradians (float) + :return: Gradians + :rtype: float + """ + return dec2gon(self.dec()) + + def gona(self): + """ + Convert to Gradians (class) + :return: Gradians + :rtype: GONAngle + """ + return GONAngle(self.gon()) + + def ddm(self): + """ + Convert to Degrees, Decimal Minutes Object + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + if self.positive: + return DDMAngle(self.degree, self.minute + (self.second/60)) + else: + return -DDMAngle(self.degree, self.minute + (self.second/60)) + + +class DDMAngle(object): + """ + Class for working with angles in Degrees, Decimal Minutes format + """ + def __init__(self, degree, minute=0.0): + """ + :param degree: Angle: whole degrees component (floats truncated) + :param minute: Angle:minutes component (floats preserved) + """ + # Convert formatted string 'DDD MM.MMMM' to DDMAngle + if type(degree) == str: + str_pts = degree.split(' ') + degree = int(str_pts[0]) + minute = float(str_pts[1]) + # Set sign of object based on sign of any variable + if degree == 0: + if str(degree)[0] == '-': + self.positive = False + elif minute < 0: + self.positive = False + else: + self.positive = True + elif degree > 0: + self.positive = True + else: # degree < 0 + self.positive = False + self.degree = abs(int(degree)) + self.minute = abs(minute) + + def __repr__(self): + if self.positive: + signsymbol = '+' + else: + signsymbol = '-' + return '{DDMAngle: ' + signsymbol + str(self.degree) + 'd ' + \ + str(self.minute) + 'm}' + + def __add__(self, other): + try: + return dec2ddm(self.dec() + other.dec()) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __radd__(self, other): + try: + return dec2ddm(other.dec() + self.dec()) + except AttributeError: + raise TypeError('Can only add Angle objects with .dec() method ' + 'together') + + def __sub__(self, other): + try: + return dec2ddm(self.dec() - other.dec()) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __rsub__(self, other): + try: + return dec2ddm(other.dec() - self.dec()) + except AttributeError: + raise TypeError('Can only subtract Angle objects with .dec() method' + ' together') + + def __mul__(self, other): + try: + return dec2ddm(self.dec() * other) + except TypeError: + raise TypeError('Multiply only defined between DMSAngle Object ' + 'and Int or Float') + + def __rmul__(self, other): + try: + return dec2ddm(other * self.dec()) + except TypeError: + raise TypeError('Multiply only defined between DMSAngle Object ' + 'and Int or Float') + + def __truediv__(self, other): + try: + return dec2ddm(self.dec() / other) + except TypeError: + raise TypeError('Division only defined between DMSAngle Object ' + 'and Int or Float') + + def __abs__(self): + return DDMAngle(self.degree, self.minute) + + def __neg__(self): + if self.positive: + return DDMAngle(-self.degree, -self.minute) + else: # sign == -1 + return DDMAngle(self.degree, self.minute) + + def __eq__(self, other): + return self.dec() == other.dec() + + def __ne__(self, other): + return self.dec() != other.dec() + + def __lt__(self, other): + return self.dec() < other.dec() + + def __gt__(self, other): + return self.dec() > other.dec() + + def __str__(self): + if self.positive: + return str(self.degree) + ' ' + str(self.minute) + else: + return '-' + str(self.degree) + ' ' + str(self.minute) + + def __round__(self, n=None): + if self.positive: + return DDMAngle(self.degree, round(self.minute, n)) + else: + return DDMAngle(-self.degree, -round(self.minute, n)) + + def rad(self): + """ + Convert to Radians + :return: Radians + :rtype: float + """ + return radians(self.dec()) + + def dec(self): + """ + Convert to Decimal Degrees (float) + :return: Decimal Degrees + :rtype: float + """ + if self.positive: + return self.degree + (self.minute / 60) + else: + return -(self.degree + (self.minute / 60)) + + def deca(self): + """ + Convert to Decimal Degrees (class) + :return: Decimal Degrees + :rtype: DECAngle + """ + return DECAngle(self.dec()) + + def hp(self): + """ + Convert to HP Notation (float) + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + minute_int, second = divmod(self.minute, 1) + if self.positive: + return self.degree + (minute_int / 100) + (second * 0.006) + else: + return -(self.degree + (minute_int / 100) + (second * 0.006)) + + def hpa(self): + """ + Convert to HP Notation (class) + :return: HP Notation (DDD.MMSSSS) + :rtype: HPAngle + """ + return HPAngle(self.hp()) + + def gon(self): + """ + Convert to Gradians (float) + :return: Gradians + :rtype: float + """ + return dec2gon(self.dec()) + + def gona(self): + """ + Convert to Gradians (class) + :return: Gradians + :rtype: GONAngle + """ + return GONAngle(self.gon()) + + def dms(self): + """ + Convert to Degrees, Minutes, Seconds Object + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + minute_int, second = divmod(self.minute, 1) + if self.positive: + return DMSAngle(self.degree, int(minute_int), second * 60) + else: + return -DMSAngle(self.degree, int(minute_int), second * 60) + + +# Functions converting from Decimal Degrees (float) to other formats + +def dec2hp(dec): + """ + Converts Decimal Degrees to HP Notation (float) + :param dec: Decimal Degrees + :type dec: float + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + minute, second = divmod(abs(dec) * 3600, 60) + degree, minute = divmod(minute, 60) + hp = degree + (minute / 100) + (second / 10000) + hp = round(hp, 16) + return hp if dec >= 0 else -hp + + +def dec2hpa(dec): + """ + Converts Decimal Degrees to HP Angle Object + :param dec: Decimal Degrees + :type dec: float + :return: HP Angle Object (DDD.MMSSSS) + :rtype: HPAngle + """ + return HPAngle(dec2hp(dec)) + + +def dec2gon(dec): + """ + Converts Decimal Degrees to Gradians + :param dec: Decimal Degrees + :type dec: float + :return: Gradians + :rtype: float + """ + return 10/9 * dec + + +def dec2gona(dec): + """ + Converts Decimal Degrees to Gradians (class) + :param dec: Decimal Degrees + :type dec: float + :return: Gradians + :rtype: GONAngle + """ + return GONAngle(dec2gon(dec)) + + +def dec2dms(dec): + """ + Converts Decimal Degrees to Degrees, Minutes, Seconds Object + :param dec: Decimal Degrees + :type dec: float + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + minute, second = divmod(abs(dec) * 3600, 60) + degree, minute = divmod(minute, 60) + return (DMSAngle(degree, minute, second) if dec >= 0 + else DMSAngle(-degree, minute, second)) + + +def dec2ddm(dec): + """ + Converts Decimal Degrees to Degrees, Decimal Minutes Object + :param dec: Decimal Degrees + :type dec: float + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + minute, second = divmod(abs(dec) * 3600, 60) + degree, minute = divmod(minute, 60) + minute = minute + (second / 60) + return DDMAngle(degree, minute) if dec >= 0 else DDMAngle(-degree, minute) + + +# Functions converting from Hewlett-Packard (HP) format to other formats + +def hp2dec(hp): + """ + Converts HP Notation to Decimal Degrees + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: Decimal Degrees + :rtype: float + """ + # Check if 1st and 3rd decimal place greater than 5 (invalid HP Notation) + hp = float(hp) + hp_dec_str = f'{hp:.17f}'.split('.')[1] + if int(hp_dec_str[0]) > 5: + raise ValueError(f'Invalid HP Notation: 1st decimal place greater ' + f'than 5: {hp}') + if len(hp_dec_str) > 2: + if int(hp_dec_str[2]) > 5: + raise ValueError(f'Invalid HP Notation: 3st decimal place greater ' + f'than 5: {hp}') + degmin, second = divmod(abs(hp) * 1000, 10) + degree, minute = divmod(degmin, 100) + dec = degree + (minute / 60) + (second / 360) + dec = round(dec, 16) + return dec if hp >= 0 else -dec + + +def hp2deca(hp): + """ + Converts HP Notation to DECAngle Object + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: Decimal Degrees Object + :rtype: DECAngle + """ + return DECAngle(hp2dec(hp)) + + +def hp2rad(hp): + """ + Converts HP Notation to radians + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: radians + :rtype: float + """ + return radians(hp2dec(hp)) + + +def hp2gon(hp): + """ + Converts HP Notation to Gradians + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: Gradians + :rtype: float + """ + return dec2gon(hp2dec(hp)) + + +def hp2gona(hp): + """ + Converts HP Notation to Gradians (class) + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: Gradians + :rtype: GONAngle + """ + return GONAngle(hp2gon(hp)) + + +def hp2dms(hp): + """ + Converts HP Notation to Degrees, Minutes, Seconds Object + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + degmin, second = divmod(abs(hp) * 1000, 10) + degree, minute = divmod(degmin, 100) + return (DMSAngle(degree, minute, second * 10) if hp >= 0 + else DMSAngle(-degree, minute, second * 10)) + + +def hp2ddm(hp): + """ + Converts HP Notation to Degrees, Decimal Minutes Object + :param hp: HP Notation (DDD.MMSSSS) + :type hp: float + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + degmin, second = divmod(abs(hp) * 1000, 10) + degree, minute = divmod(degmin, 100) + minute = minute + (second / 6) + return DDMAngle(degree, minute) if hp >= 0 else DDMAngle(-degree, minute) + + +# Functions converting from Gradians format to other formats + +def gon2dec(gon): + """ + Converts Gradians to Decimal Degrees + :param gon: Gradians + :type gon: float + :return: Decimal Degrees + :rtype: float + """ + return 9/10 * gon + + +def gon2deca(gon): + """ + Converts Gradians to DECAngle Object + :param gon: Gradians + :type gon: float + :return: Decimal Degrees Object + :rtype: DECAngle + """ + return DECAngle(gon2dec(gon)) + + +def gon2hp(gon): + """ + Converts Gradians to HP Notation (float) + :param gon: Gradians + :type gon: float + :return: HP Notation (DDD.MMSSSS) + :rtype: float + """ + return dec2hp(gon2dec(gon)) + + +def gon2hpa(gon): + """ + Converts Gradians to HP Angle Object + :param gon: Gradians + :type gon: float + :return: HP Angle Object (DDD.MMSSSS) + :rtype: HPAngle + """ + return HPAngle(gon2hp(gon)) + + +def gon2rad(gon): + """ + Converts Gradians to radians + :param gon: Gradians + :type gon: float + :return: Radians + :rtype: float + """ + return radians(gon2dec(gon)) + + +def gon2dms(gon): + """ + Converts Gradians to Degrees, Minutes, Seconds Object + :param gon: Gradians + :type gon: float + :return: Degrees, Minutes, Seconds Object + :rtype: DMSAngle + """ + return dec2dms(gon2dec(gon)) + + +def gon2ddm(gon): + """ + Converts Gradians to Degrees, Decimal Minutes Object + :param gon: Gradians + :type gon: float + :return: Degrees, Decimal Minutes Object + :rtype: DDMAngle + """ + return dec2ddm(gon2dec(gon)) + + +# Miscellaneous other useful functions + +def dd2sec(dd): + """ + Converts angle in decimal degrees to angle in seconds + :param dd: Decimal Degrees + :return: Seconds + """ + minute, second = divmod(abs(dd) * 3600, 60) + degree, minute = divmod(minute, 60) + sec = (degree * 3600) + (minute * 60) + second + return sec if dd >= 0 else -sec + + +def dec2hp_v(dec): + minute, second = divmod(abs(dec) * 3600, 60) + degree, minute = divmod(minute, 60) + hp = degree + (minute / 100) + (second / 10000) + hp[dec <= 0] = -hp[dec <= 0] + return hp + + +def hp2dec_v(hp): + degmin, second = divmod(abs(hp) * 1000, 10) + degree, minute = divmod(degmin, 100) + dec = degree + (minute / 60) + (second / 360) + dec[hp <= 0] = -dec[hp <= 0] + return dec + + +def angular_typecheck(angle): + # Converts Angle Objects to Decimal Degrees (float) for computations + supported_types = [DMSAngle, DDMAngle, DECAngle, HPAngle, GONAngle] + if type(angle) in supported_types: + return angle.dec() + else: + return angle diff --git a/geodepy/convert.py b/geodepy/convert.py index 30438f7..4c2a7f0 100644 --- a/geodepy/convert.py +++ b/geodepy/convert.py @@ -9,365 +9,14 @@ sqrt, cosh, sinh, tan, atan, log) import datetime from geodepy.constants import utm, grs80 +from geodepy.angles import (DECAngle, DMSAngle, DDMAngle, HPAngle, + hp2dec, hp2dms, hp2ddm, dec2hp, dec2dms, dec2ddm, + dd2sec, dec2hp_v, hp2dec_v, angular_typecheck) # Universal Transverse Mercator Projection Parameters proj = utm -class DMSAngle(object): - """ - Class for working with angles in Degrees, Minutes and Seconds format - """ - def __init__(self, degree=0, minute=0, second=0.0): - """ - :param degree: Angle: whole degrees component (floats truncated) - :param minute: Angle: whole minutes component (floats truncated) - :param second: Angle: seconds component (floats preserved) - """ - # Set sign of object based on sign of any variable - if degree == 0: - if str(degree)[0] == '-': - self.sign = -1 - elif minute < 0: - self.sign = -1 - elif second < 0: - self.sign = -1 - else: - self.sign = 1 - elif degree > 0: - self.sign = 1 - else: # degree < 0 - self.sign = -1 - self.degree = abs(int(degree)) - self.minute = abs(int(minute)) - self.second = abs(second) - - def __repr__(self): - if self.sign == -1: - signsymbol = '-' - elif self.sign == 1: - signsymbol = '+' - else: - signsymbol = 'error' - return '{DMSAngle: ' + signsymbol + str(self.degree) + 'd ' +\ - str(self.minute) + 'm ' + str(self.second) + 's}' - - def __add__(self, other): - try: - return dec2dms(self.dec() + other.dec()) - except AttributeError: - raise TypeError('Can only add DMSAngle and/or DDMAngle objects ' - 'together') - - def __radd__(self, other): - try: - return dec2dms(other.dec() + self.dec()) - except AttributeError: - raise TypeError('Can only add DMSAngle and/or DDMAngle objects ' - 'together') - - def __sub__(self, other): - try: - return dec2dms(self.dec() - other.dec()) - except AttributeError: - raise TypeError('Can only subtract DMSAngle and/or DDMAngle ' - 'objects together') - - def __rsub__(self, other): - try: - return dec2dms(other.dec() - self.dec()) - except AttributeError: - raise TypeError('Can only subtract DMSAngle and/or DDMAngle ' - 'objects together') - - def __mul__(self, other): - try: - return dec2dms(self.dec() * other) - except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') - - def __rmul__(self, other): - try: - return dec2dms(other * self.dec()) - except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') - - def __truediv__(self, other): - try: - return dec2dms(self.dec() / other) - except TypeError: - raise TypeError('Division only defined between DMSAngle Object ' - 'and Int or Float') - - def __abs__(self): - return DMSAngle(self.degree, self.minute, self.second) - - def __neg__(self): - if self.sign == 1: - return DMSAngle(-self.degree, -self.minute, -self.second) - else: # sign == -1 - return DMSAngle(self.degree, self.minute, self.second) - - def __eq__(self, other): - return self.dec() == other.dec() - - def __ne__(self, other): - return self.dec() != other.dec() - - def __lt__(self, other): - return self.dec() < other.dec() - - def __gt__(self, other): - return self.dec() > other.dec() - - def dec(self): - """ - Convert to Decimal Degrees - :return: Decimal Degrees - :rtype: float - """ - return self.sign * (self.degree + (self.minute / 60) + - (self.second / 3600)) - - def hp(self): - """ - Convert to HP Notation - :return: HP Notation (DDD.MMSSSS) - :rtype: float - """ - return self.sign * (self.degree + (self.minute / 100) + - (self.second / 10000)) - - def ddm(self): - """ - Convert to Degrees, Decimal Minutes Object - :return: Degrees, Decimal Minutes Object - :rtype: DDMAngle - """ - if self.sign == 1: - return DDMAngle(self.degree, self.minute + (self.second/60)) - else: # sign == -1 - return -DDMAngle(self.degree, self.minute + (self.second/60)) - - -class DDMAngle(object): - """ - Class for working with angles in Degrees, Decimal Minutes format - """ - def __init__(self, degree=0, minute=0.0): - """ - :param degree: Angle: whole degrees component (floats truncated) - :param minute: Angle:minutes component (floats preserved) - """ - # Set sign of object based on sign of any variable - if degree == 0: - if str(degree)[0] == '-': - self.sign = -1 - elif minute < 0: - self.sign = -1 - else: - self.sign = 1 - elif degree > 0: - self.sign = 1 - else: # degree < 0 - self.sign = -1 - self.degree = abs(int(degree)) - self.minute = abs(minute) - - def __repr__(self): - if self.sign == -1: - signsymbol = '-' - elif self.sign == 1: - signsymbol = '+' - else: - signsymbol = 'error' - return '{DDMAngle: ' + signsymbol + str(self.degree) + 'd ' + \ - str(self.minute) + 'm}' - - def __add__(self, other): - try: - return dec2ddm(self.dec() + other.dec()) - except AttributeError: - raise TypeError('Can only add DMSAngle and/or DDMAngle objects ' - 'together') - - def __radd__(self, other): - try: - return dec2ddm(other.dec() + self.dec()) - except AttributeError: - raise TypeError('Can only add DMSAngle and/or DDMAngle objects ' - 'together') - - def __sub__(self, other): - try: - return dec2ddm(self.dec() - other.dec()) - except AttributeError: - raise TypeError('Can only add DMSAngle and/or DDMAngle objects ' - 'together') - - def __rsub__(self, other): - try: - return dec2ddm(other.dec() - self.dec()) - except AttributeError: - raise TypeError('Can only add DMSAngle and/or DDMAngle objects ' - 'together') - - def __mul__(self, other): - try: - return dec2ddm(self.dec() * other) - except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') - - def __rmul__(self, other): - try: - return dec2ddm(other * self.dec()) - except TypeError: - raise TypeError('Multiply only defined between DMSAngle Object ' - 'and Int or Float') - - def __truediv__(self, other): - try: - return dec2ddm(self.dec() / other) - except TypeError: - raise TypeError('Division only defined between DMSAngle Object ' - 'and Int or Float') - - def __abs__(self): - return DDMAngle(self.degree, self.minute) - - def __neg__(self): - if self.sign == 1: - return DDMAngle(-self.degree, -self.minute) - else: # sign == -1 - return DDMAngle(self.degree, self.minute) - - def __eq__(self, other): - return self.dec() == other.dec() - - def __ne__(self, other): - return self.dec() != other.dec() - - def __lt__(self, other): - return self.dec() < other.dec() - - def __gt__(self, other): - return self.dec() > other.dec() - - def dec(self): - """ - Convert to Decimal Degrees - :return: Decimal Degrees - :rtype: float - """ - return self.sign * (self.degree + (self.minute / 60)) - - def hp(self): - """ - Convert to HP Notation - :return: HP Notation (DDD.MMSSSS) - :rtype: float - """ - minute_int, second = divmod(self.minute, 1) - return self.sign * (self.degree + (minute_int / 100) + - (second * 0.006)) - - def dms(self): - """ - Convert to Degrees, Minutes, Seconds Object - :return: Degrees, Minutes, Seconds Object - :rtype: DMSAngle - """ - minute_int, second = divmod(self.minute, 1) - return self.sign * DMSAngle(self.degree, minute_int, second * 60) - - -def dec2hp(dec): - """ - Converts Decimal Degrees to HP Notation - :param dec: Decimal Degrees - :type dec: float - :return: HP Notation (DDD.MMSSSS) - :rtype: float - """ - minute, second = divmod(abs(dec) * 3600, 60) - degree, minute = divmod(minute, 60) - hp = degree + (minute / 100) + (second / 10000) - return hp if dec >= 0 else -hp - - -def hp2dec(hp): - """ - Converts HP Notation to Decimal Degrees - :param hp: HP Notation (DDD.MMSSSS) - :type hp: float - :return: Decimal Degrees - :rtype: float - """ - degmin, second = divmod(abs(hp) * 1000, 10) - degree, minute = divmod(degmin, 100) - dec = degree + (minute / 60) + (second / 360) - return dec if hp >= 0 else -dec - - -def dec2dms(dec): - """ - Converts Decimal Degrees to Degrees, Minutes, Seconds Object - :param dec: Decimal Degrees - :type dec: float - :return: Degrees, Minutes, Seconds Object - :rtype: DMSAngle - """ - minute, second = divmod(abs(dec) * 3600, 60) - degree, minute = divmod(minute, 60) - return (DMSAngle(degree, minute, second) if dec >= 0 - else DMSAngle(-degree, minute, second)) - - -def dec2ddm(dec): - """ - Converts Decimal Degrees to Degrees, Decimal Minutes Object - :param dec: Decimal Degrees - :type dec: float - :return: Degrees, Decimal Minutes Object - :rtype: DDMAngle - """ - minute, second = divmod(abs(dec) * 3600, 60) - degree, minute = divmod(minute, 60) - minute = minute + (second / 60) - return DDMAngle(degree, minute) if dec >= 0 else DDMAngle(-degree, minute) - - -def hp2dms(hp): - """ - Converts HP Notation to Degrees, Minutes, Seconds Object - :param hp: HP Notation (DDD.MMSSSS) - :type hp: float - :return: Degrees, Minutes, Seconds Object - :rtype: DMSAngle - """ - degmin, second = divmod(abs(hp) * 1000, 10) - degree, minute = divmod(degmin, 100) - return (DMSAngle(degree, minute, second * 10) if hp >= 0 - else DMSAngle(-degree, minute, second * 10)) - - -def hp2ddm(hp): - """ - Converts HP Notation to Degrees, Decimal Minutes Object - :param hp: HP Notation (DDD.MMSSSS) - :type hp: float - :return: Degrees, Decimal Minutes Object - :rtype: DDMAngle - """ - degmin, second = divmod(abs(hp) * 1000, 10) - degree, minute = divmod(degmin, 100) - minute = minute + (second / 6) - return DDMAngle(degree, minute) if hp >= 0 else DDMAngle(-degree, minute) - - def polar2rect(r, theta): """ Converts point in polar coordinates to corresponding rectangular coordinates @@ -404,42 +53,6 @@ def rect2polar(x, y): return r, theta -def dd2sec(dd): - """ - Converts angle in decimal degrees to angle in seconds - :param dd: Decimal Degrees - :return: Seconds - """ - minute, second = divmod(abs(dd) * 3600, 60) - degree, minute = divmod(minute, 60) - sec = (degree * 3600) + (minute * 60) + second - return sec if dd >= 0 else -sec - - -def dec2hp_v(dec): - minute, second = divmod(abs(dec) * 3600, 60) - degree, minute = divmod(minute, 60) - hp = degree + (minute / 100) + (second / 10000) - hp[dec <= 0] = -hp[dec <= 0] - return hp - - -def hp2dec_v(hp): - degmin, second = divmod(abs(hp) * 1000, 10) - degree, minute = divmod(degmin, 100) - dec = degree + (minute / 60) + (second / 360) - dec[hp <= 0] = -dec[hp <= 0] - return dec - - -def angular_typecheck(angle): - # Converts DMSAngle and DDMAngle Objects to Decimal Degrees - if type(angle) is DMSAngle or type(angle) is DDMAngle: - return angle.dec() - else: - return angle - - def rect_radius(ellipsoid): """ Computes the Rectifying Radius of an Ellipsoid with specified Inverse diff --git a/geodepy/tests/test_angles.py b/geodepy/tests/test_angles.py new file mode 100644 index 0000000..9c802d2 --- /dev/null +++ b/geodepy/tests/test_angles.py @@ -0,0 +1,606 @@ +import unittest +from math import radians + +from geodepy.angles import (dec2hp, hp2dec, + DMSAngle, DDMAngle, HPAngle, DECAngle, GONAngle, + dec2dms, dec2ddm, dec2hpa, dec2gon, dec2gona, + hp2dms, hp2ddm, hp2rad, hp2deca, hp2gon, hp2gona, + gon2dec, gon2deca, gon2rad, gon2hp, gon2hpa, + gon2dms, gon2ddm, + dd2sec, angular_typecheck) + +rad_exs = [radians(123.74875), radians(12.575), radians(-12.575), + radians(0.0525), radians(0.005)] + +dec_ex = 123.74875 +dec_ex2 = 12.575 +dec_ex3 = -12.575 +dec_ex4 = 0.0525 +dec_ex5 = 0.005 +dec_exs = [dec_ex, dec_ex2, dec_ex3, dec_ex4, dec_ex5] + +deca_ex = DECAngle(123.74875) +deca_ex2 = DECAngle(12.575) +deca_ex3 = DECAngle(-12.575) +deca_ex4 = DECAngle(0.0525) +deca_ex5 = DECAngle(0.005) +deca_exs = [deca_ex, deca_ex2, deca_ex3, deca_ex4, deca_ex5] + +hp_ex = 123.44555 +hp_ex2 = 12.3430 +hp_ex3 = -12.3430 +hp_ex4 = 0.0309 +hp_ex5 = 0.0018 +hp_exs = [hp_ex, hp_ex2, hp_ex3, hp_ex4, hp_ex5] + +hpa_ex = HPAngle(123.44555) +hpa_ex2 = HPAngle(12.3430) +hpa_ex3 = HPAngle(-12.3430) +hpa_ex4 = HPAngle(0.0309) +hpa_ex5 = HPAngle(0.0018) +hpa_exs = [hpa_ex, hpa_ex2, hpa_ex3, hpa_ex4, hpa_ex5] + +dms_ex = DMSAngle(123, 44, 55.5) +dms_ex2 = DMSAngle(12, 34, 30) +dms_ex3 = DMSAngle(-12, -34, -30) +dms_ex4 = DMSAngle(0, 3, 9) +dms_ex5 = DMSAngle(0, 0, 18) +dms_exs = [dms_ex, dms_ex2, dms_ex3, dms_ex4, dms_ex5] + +dms_str = '123 44 55.5' +dms_str2 = '12 34 30' +dms_str3 = '-12 34 30' +dms_str4 = '0 3 9' +dms_str5 = '0 0 18' +dms_strs = [dms_str, dms_str2, dms_str3, dms_str4, dms_str5] + +ddm_ex = DDMAngle(123, 44.925) +ddm_ex2 = DDMAngle(12, 34.5) +ddm_ex3 = DDMAngle(-12, -34.5) +ddm_ex4 = DDMAngle(0, 3.15) +ddm_ex5 = DDMAngle(0, 0.3) +ddm_exs = [ddm_ex, ddm_ex2, ddm_ex3, ddm_ex4, ddm_ex5] + +ddm_str = '123 44.925' +ddm_str2 = '12 34.5' +ddm_str3 = '-12 34.5' +ddm_str4 = '0 3.15' +ddm_str5 = '0 0.3' +ddm_strs = [ddm_str, ddm_str2, ddm_str3, ddm_str4, ddm_str5] + +gon_ex = 137.4986111111111 +gon_ex2 = 13.97222222222222 +gon_ex3 = -13.97222222222222 +gon_ex4 = 0.05833333333333333 +gon_ex5 = 0.00555555555555555 +gon_exs = [gon_ex, gon_ex2, gon_ex3, gon_ex4, gon_ex5] + +gona_ex = GONAngle(137.4986111111111) +gona_ex2 = GONAngle(13.97222222222222) +gona_ex3 = GONAngle(-13.97222222222222) +gona_ex4 = GONAngle(0.05833333333333333) +gona_ex5 = GONAngle(0.00555555555555555) +gona_exs = [gona_ex, gona_ex2, gona_ex3, gona_ex4, gona_ex5] + + +class TestConvert(unittest.TestCase): + def test_DECAngle(self): + # Test DECAngle Methods + for num, ex in enumerate(deca_exs): + self.assertEqual(ex.rad(), rad_exs[num]) + self.assertEqual(-ex.rad(), -rad_exs[num]) + self.assertEqual(ex.dec(), dec_exs[num]) + self.assertEqual(-ex.dec(), -dec_exs[num]) + self.assertEqual(round(ex.hpa(), 13), hpa_exs[num]) + self.assertEqual(round(-ex.hpa(), 13), -hpa_exs[num]) + self.assertEqual(ex.hp(), hp_exs[num]) + self.assertEqual(-ex.hp(), -hp_exs[num]) + self.assertAlmostEqual(ex.gon(), gon_exs[num], 13) + self.assertAlmostEqual(-ex.gon(), -gon_exs[num], 13) + self.assertEqual(round(ex.gona(), 13), round(gona_exs[num], 13)) + self.assertEqual(round(-ex.gona(), 13), round(-gona_exs[num], 13)) + self.assertEqual(round(ex.dms(), 10), dms_exs[num]) + self.assertEqual(round(-ex.dms(), 10), -dms_exs[num]) + self.assertEqual(round(ex.ddm(), 12), ddm_exs[num]) + self.assertEqual(round(-ex.ddm(), 12), -ddm_exs[num]) + self.assertEqual(str(ex), str(dec_exs[num])) + self.assertEqual(DECAngle(str(ex)), ex) + self.assertEqual(int(ex), int(dec_exs[num])) + self.assertEqual(float(ex), dec_exs[num]) + self.assertEqual(round(ex), DECAngle(round(dec_exs[num]))) + + # Test HPAngle Representation + self.assertEqual(repr(deca_ex), '{DECAngle: +123.74875}') + self.assertEqual(repr(deca_ex3), '{DECAngle: -12.575}') + + # Test DECAngle Overloads + self.assertEqual(dec_ex + dec_ex2, (deca_ex + deca_ex2).dec()) + self.assertEqual(dec_ex2 + dec_ex, (deca_ex2 + deca_ex).dec()) + self.assertEqual(dec_ex - dec_ex2, (deca_ex - deca_ex2).dec()) + self.assertEqual(dec_ex2 - dec_ex, (deca_ex2 - deca_ex).dec()) + self.assertEqual(dec_ex * 5, (deca_ex * 5).dec()) + self.assertEqual(5 * dec_ex, (5 * deca_ex).dec()) + self.assertEqual(dec_ex / 3, (deca_ex / 3).dec()) + self.assertEqual(abs(-deca_ex), deca_ex) + self.assertEqual(-deca_ex2, deca_ex3) + self.assertEqual(deca_ex2, abs(deca_ex3)) + self.assertTrue(deca_ex == deca_ex) + self.assertFalse(deca_ex == deca_ex2) + self.assertTrue(deca_ex != deca_ex2) + self.assertFalse(deca_ex != deca_ex) + self.assertTrue(deca_ex > deca_ex2) + self.assertFalse(deca_ex2 > deca_ex) + self.assertTrue(deca_ex2 < deca_ex) + self.assertFalse(deca_ex < deca_ex2) + with self.assertRaises(TypeError): + deca_ex * 'a' + with self.assertRaises(TypeError): + 'a' * deca_ex + with self.assertRaises(TypeError): + deca_ex / 'a' + with self.assertRaises(TypeError): + deca_ex + 'a' + with self.assertRaises(TypeError): + 'a' + deca_ex + with self.assertRaises(TypeError): + deca_ex - 'a' + with self.assertRaises(TypeError): + 'a' - deca_ex + + def test_HPAngle(self): + # Test HPAngle Methods + for num, ex in enumerate(hpa_exs): + self.assertAlmostEqual(ex.rad(), rad_exs[num], 16) + self.assertAlmostEqual(-ex.rad(), -rad_exs[num], 16) + self.assertAlmostEqual(ex.dec(), dec_exs[num], 13) + self.assertAlmostEqual(-ex.dec(), -dec_exs[num], 13) + self.assertEqual(round(ex.deca(), 13), deca_exs[num]) + self.assertEqual(round(-ex.deca(), 13), -deca_exs[num]) + self.assertAlmostEqual(ex.hp(), hp_exs[num], 16) + self.assertAlmostEqual(-ex.hp(), -hp_exs[num], 16) + self.assertAlmostEqual(ex.gon(), gon_exs[num], 13) + self.assertAlmostEqual(-ex.gon(), -gon_exs[num], 13) + self.assertEqual(round(ex.gona(), 13), round(gona_exs[num], 13)) + self.assertEqual(round(-ex.gona(), 13), round(-gona_exs[num], 13)) + self.assertEqual(round(ex.dms(), 10), dms_exs[num]) + self.assertEqual(round(-ex.dms(), 10), -dms_exs[num]) + self.assertEqual(round(ex.ddm(), 12), ddm_exs[num]) + self.assertEqual(round(-ex.ddm(), 12), -ddm_exs[num]) + self.assertEqual(str(ex), str(hp_exs[num])) + self.assertEqual(HPAngle(str(ex)), ex) + self.assertEqual(int(ex), int(hp_exs[num])) + self.assertEqual(float(ex), hp_exs[num]) + self.assertEqual(round(ex), HPAngle(round(hp_exs[num]))) + + # Test HPAngle Representation + self.assertEqual(repr(hpa_ex), '{HPAngle: +123.44555}') + self.assertEqual(repr(hpa_ex3), '{HPAngle: -12.343}') + + # Test DMSAngle Overloads + self.assertAlmostEqual(dec_ex + dec_ex2, (hpa_ex + hpa_ex2).dec(), 12) + self.assertAlmostEqual(dec_ex2 + dec_ex, (hpa_ex2 + hpa_ex).dec(), 12) + self.assertAlmostEqual(dec_ex - dec_ex2, (hpa_ex - hpa_ex2).dec(), 12) + self.assertAlmostEqual(dec_ex2 - dec_ex, (hpa_ex2 - hpa_ex).dec(), 12) + self.assertAlmostEqual(dec_ex * 5, (hpa_ex * 5).dec(), 12) + self.assertAlmostEqual(5 * dec_ex, (5 * hpa_ex).dec(), 12) + self.assertAlmostEqual(dec_ex / 3, (hpa_ex / 3).dec(), 12) + self.assertEqual(abs(-hpa_ex), hpa_ex) + self.assertEqual(-hpa_ex2, hpa_ex3) + self.assertEqual(hpa_ex2, abs(hpa_ex3)) + self.assertTrue(hpa_ex == hpa_ex) + self.assertFalse(hpa_ex == hpa_ex2) + self.assertTrue(hpa_ex != hpa_ex2) + self.assertFalse(hpa_ex != hpa_ex) + self.assertTrue(hpa_ex > hpa_ex2) + self.assertFalse(hpa_ex2 > hpa_ex) + self.assertTrue(hpa_ex2 < hpa_ex) + self.assertFalse(hpa_ex < hpa_ex2) + with self.assertRaises(TypeError): + hpa_ex * 'a' + with self.assertRaises(TypeError): + 'a' * hpa_ex + with self.assertRaises(TypeError): + hpa_ex / 'a' + with self.assertRaises(TypeError): + hpa_ex + 'a' + with self.assertRaises(TypeError): + 'a' + hpa_ex + with self.assertRaises(TypeError): + hpa_ex - 'a' + with self.assertRaises(TypeError): + 'a' - hpa_ex + + # Test Validity Checking of HP format input + with self.assertRaises(ValueError): + HPAngle(123.7) + with self.assertRaises(ValueError): + HPAngle(123.007) + + def test_GONAngle(self): + # Test GONAngle Methods + for num, ex in enumerate(gona_exs): + self.assertAlmostEqual(ex.rad(), rad_exs[num], 15) + self.assertAlmostEqual(-ex.rad(), -rad_exs[num], 15) + self.assertAlmostEqual(ex.dec(), dec_exs[num], 13) + self.assertAlmostEqual(-ex.dec(), -dec_exs[num], 13) + self.assertEqual(round(ex.deca(), 13), deca_exs[num]) + self.assertEqual(round(-ex.deca(), 13), -deca_exs[num]) + self.assertAlmostEqual(ex.hp(), hp_exs[num], 16) + self.assertAlmostEqual(-ex.hp(), -hp_exs[num], 16) + self.assertEqual(ex.hpa(), hpa_exs[num]) + self.assertEqual(-ex.hpa(), -hpa_exs[num]) + self.assertAlmostEqual(ex.gon(), gon_exs[num], 13) + self.assertEqual(round(ex.dms(), 10), dms_exs[num]) + self.assertEqual(round(-ex.dms(), 10), -dms_exs[num]) + self.assertEqual(round(ex.ddm(), 12), ddm_exs[num]) + self.assertEqual(round(-ex.ddm(), 12), -ddm_exs[num]) + self.assertEqual(str(ex), str(gon_exs[num])) + self.assertEqual(GONAngle(str(ex)), ex) + self.assertEqual(int(ex), int(gon_exs[num])) + self.assertEqual(float(ex), gon_exs[num]) + self.assertEqual(round(ex), GONAngle(round(gon_exs[num]))) + + # Test GONAngle Representation + self.assertEqual(repr(gona_ex), '{GONAngle: +137.4986111111111}') + self.assertEqual(repr(gona_ex3), '{GONAngle: -13.97222222222222}') + + # Test DMSAngle Overloads + self.assertAlmostEqual(dec_ex + dec_ex2, (gona_ex + gona_ex2).dec(), 12) + self.assertAlmostEqual(dec_ex2 + dec_ex, (gona_ex2 + gona_ex).dec(), 12) + self.assertAlmostEqual(dec_ex - dec_ex2, (gona_ex - gona_ex2).dec(), 12) + self.assertAlmostEqual(dec_ex2 - dec_ex, (gona_ex2 - gona_ex).dec(), 12) + self.assertAlmostEqual(dec_ex * 5, (gona_ex * 5).dec(), 12) + self.assertAlmostEqual(5 * dec_ex, (5 * gona_ex).dec(), 12) + self.assertAlmostEqual(dec_ex / 3, (gona_ex / 3).dec(), 12) + self.assertEqual(abs(-gona_ex), gona_ex) + self.assertEqual(-gona_ex2, gona_ex3) + self.assertEqual(gona_ex2, abs(gona_ex3)) + self.assertTrue(gona_ex == gona_ex) + self.assertFalse(gona_ex == gona_ex2) + self.assertTrue(gona_ex != gona_ex2) + self.assertFalse(gona_ex != gona_ex) + self.assertTrue(gona_ex > gona_ex2) + self.assertFalse(gona_ex2 > gona_ex) + self.assertTrue(gona_ex2 < gona_ex) + self.assertFalse(gona_ex < gona_ex2) + with self.assertRaises(TypeError): + gona_ex * 'a' + with self.assertRaises(TypeError): + 'a' * gona_ex + with self.assertRaises(TypeError): + gona_ex / 'a' + with self.assertRaises(TypeError): + gona_ex + 'a' + with self.assertRaises(TypeError): + 'a' + gona_ex + with self.assertRaises(TypeError): + gona_ex - 'a' + with self.assertRaises(TypeError): + 'a' - gona_ex + + def test_DMSAngle(self): + # Test DMSAngle Methods + for num, ex in enumerate(dms_exs): + self.assertAlmostEqual(ex.rad(), rad_exs[num], 16) + self.assertAlmostEqual(-ex.rad(), -rad_exs[num], 16) + self.assertAlmostEqual(ex.dec(), dec_exs[num], 16) + self.assertAlmostEqual(-ex.dec(), -dec_exs[num], 16) + self.assertEqual(round(ex.deca(), 16), deca_exs[num]) + self.assertEqual(round(-ex.deca(), 16), -deca_exs[num]) + self.assertAlmostEqual(ex.hp(), hp_exs[num], 16) + self.assertAlmostEqual(-ex.hp(), -hp_exs[num], 16) + self.assertEqual(ex.hpa(), hpa_exs[num]) + self.assertEqual(-ex.hpa(), -hpa_exs[num]) + self.assertAlmostEqual(ex.gon(), gon_exs[num], 13) + self.assertAlmostEqual(-ex.gon(), -gon_exs[num], 13) + self.assertEqual(round(ex.gona(), 13), round(gona_exs[num], 13)) + self.assertEqual(round(-ex.gona(), 13), round(-gona_exs[num], 13)) + self.assertEqual(ex.ddm(), ddm_exs[num]) + self.assertEqual(-ex.ddm(), -ddm_exs[num]) + self.assertEqual((round(ex, 2)).hp(), round(hp_exs[num], 5)) + self.assertEqual((round(-ex, 2)).hp(), round(-hp_exs[num], 5)) + + # Test DMSAngle Sign Conventions + self.assertEqual(-dec_ex, DMSAngle(-dms_ex.degree, dms_ex.minute, + dms_ex.second).dec()) + self.assertEqual(dec_ex, DMSAngle(dms_ex.degree, -dms_ex.minute, + -dms_ex.second).dec()) + self.assertAlmostEqual(-dec_ex4, DMSAngle(0, -dms_ex4.minute, + dms_ex4.second).dec(), 9) + self.assertAlmostEqual(dec_ex4, DMSAngle(0, dms_ex4.minute, + dms_ex4.second).dec(), 9) + self.assertEqual(-dec_ex5, DMSAngle(0, 0, -dms_ex5.second).dec()) + self.assertEqual(dec_ex5, DMSAngle(0, 0, dms_ex5.second).dec()) + self.assertEqual(-dms_ex3, DMSAngle(12, 34, -30)) + self.assertTrue(dms_ex.positive) + self.assertFalse((-dms_ex).positive) + self.assertTrue(dms_ex4.positive) + self.assertFalse((-dms_ex4).positive) + self.assertTrue(dms_ex5.positive) + self.assertFalse((-dms_ex5).positive) + self.assertFalse(DMSAngle(-1, 2, 3).positive) + self.assertTrue(DMSAngle(1, -2, 3).positive) + self.assertTrue(DMSAngle(1, 2, -3).positive) + self.assertFalse(DMSAngle(0, -1, 2).positive) + self.assertFalse(DMSAngle(0, 0, -3).positive) + self.assertTrue(DMSAngle(-0, 1, 2).positive) + self.assertFalse(DMSAngle(-0.0, 1, 2).positive) + self.assertEqual(repr(dms_ex), '{DMSAngle: +123d 44m 55.5s}') + self.assertEqual(repr(dms_ex3), '{DMSAngle: -12d 34m 30s}') + + # Test DMSAngle Overloads + self.assertEqual(dec_ex + dec_ex2, (dms_ex + dms_ex2).dec()) + self.assertEqual(dec_ex2 + dec_ex, (dms_ex2 + dms_ex).dec()) + self.assertEqual(dec_ex - dec_ex2, (dms_ex - dms_ex2).dec()) + self.assertEqual(dec_ex2 - dec_ex, (dms_ex2 - dms_ex).dec()) + self.assertEqual(dec_ex * 5, (dms_ex * 5).dec()) + self.assertEqual(5 * dec_ex, (5 * dms_ex).dec()) + self.assertEqual(dec_ex / 3, (dms_ex / 3).dec()) + self.assertEqual(abs(-dms_ex), dms_ex) + self.assertEqual(-dms_ex2, dms_ex3) + self.assertEqual(dms_ex2, abs(dms_ex3)) + self.assertEqual(dms_ex, ddm_ex) + self.assertTrue(dms_ex == dms_ex) + self.assertFalse(dms_ex == dms_ex2) + self.assertTrue(dms_ex != dms_ex2) + self.assertFalse(dms_ex != dms_ex) + self.assertTrue(dms_ex > dms_ex2) + self.assertFalse(dms_ex2 > dms_ex) + self.assertTrue(dms_ex2 < dms_ex) + self.assertFalse(dms_ex < dms_ex2) + with self.assertRaises(TypeError): + dms_ex * 'a' + with self.assertRaises(TypeError): + 'a' * dms_ex + with self.assertRaises(TypeError): + dms_ex / 'a' + with self.assertRaises(TypeError): + dms_ex + 'a' + with self.assertRaises(TypeError): + 'a' + dms_ex + with self.assertRaises(TypeError): + dms_ex - 'a' + with self.assertRaises(TypeError): + 'a' - dms_ex + + # Test reading in formatted strings + for num, ex in enumerate(dms_strs): + self.assertEqual(DMSAngle(ex), dms_exs[num]) + self.assertEqual(ex, str(dms_exs[num])) + + def test_DDMAngle(self): + # Test DDMAngle Methods + for num, ex in enumerate(ddm_exs): + self.assertAlmostEqual(ex.rad(), rad_exs[num], 16) + self.assertAlmostEqual(-ex.rad(), -rad_exs[num], 16) + self.assertAlmostEqual(ex.dec(), dec_exs[num], 16) + self.assertAlmostEqual(-ex.dec(), -dec_exs[num], 16) + self.assertEqual(round(ex.deca(), 16), deca_exs[num]) + self.assertEqual(round(-ex.deca(), 16), -deca_exs[num]) + self.assertAlmostEqual(ex.hp(), hp_exs[num], 16) + self.assertAlmostEqual(-ex.hp(), -hp_exs[num], 16) + self.assertEqual(ex.hpa(), hpa_exs[num]) + self.assertEqual(-ex.hpa(), -hpa_exs[num]) + self.assertAlmostEqual(ex.gon(), gon_exs[num], 13) + self.assertAlmostEqual(-ex.gon(), -gon_exs[num], 13) + self.assertEqual(round(ex.gona(), 13), round(gona_exs[num], 13)) + self.assertEqual(round(-ex.gona(), 13), round(-gona_exs[num], 13)) + self.assertEqual(round(ex.dms(), 13), dms_exs[num]) + self.assertEqual(round(-ex.dms(), 13), -dms_exs[num]) + self.assertEqual((round(ex)).hp(), round(hp_exs[num], 2)) + self.assertEqual((round(-ex)).hp(), round(-hp_exs[num], 2)) + + # Test DMSAngle Sign Conventions + self.assertEqual(-dec_ex, DDMAngle(-dms_ex.degree, + ddm_ex.minute).dec()) + self.assertEqual(dec_ex, DDMAngle(dms_ex.degree, -ddm_ex.minute).dec()) + self.assertAlmostEqual(-dec_ex4, DDMAngle(0, -ddm_ex4.minute).dec(), 9) + self.assertAlmostEqual(dec_ex4, DDMAngle(0, ddm_ex4.minute).dec(), 9) + self.assertEqual(-ddm_ex3, DDMAngle(12, 34.5)) + self.assertTrue(ddm_ex.positive) + self.assertFalse((-ddm_ex).positive) + self.assertTrue(ddm_ex4.positive) + self.assertFalse((-ddm_ex4).positive) + self.assertTrue(ddm_ex5.positive) + self.assertFalse((-ddm_ex5).positive) + self.assertFalse(DDMAngle(-1, 2).positive) + self.assertTrue(DDMAngle(1, -2).positive) + self.assertTrue(DDMAngle(1, 2).positive) + self.assertFalse(DDMAngle(0, -1).positive) + self.assertTrue(DDMAngle(-0, 1).positive) + self.assertFalse(DDMAngle(-0.0, 1).positive) + self.assertEqual(repr(ddm_ex), '{DDMAngle: +123d 44.925m}') + self.assertEqual(repr(ddm_ex3), '{DDMAngle: -12d 34.5m}') + + # Test DDMAngle Overloads + self.assertEqual(dec_ex + dec_ex2, (ddm_ex + ddm_ex2).dec()) + self.assertEqual(dec_ex2 + dec_ex, (ddm_ex2 + ddm_ex).dec()) + self.assertEqual(dec_ex - dec_ex2, (ddm_ex - ddm_ex2).dec()) + self.assertEqual(dec_ex2 - dec_ex, (ddm_ex2 - ddm_ex).dec()) + self.assertEqual(dec_ex * 5, (ddm_ex * 5).dec()) + self.assertEqual(5 * dec_ex, (5 * ddm_ex).dec()) + self.assertEqual(dec_ex / 3, (ddm_ex / 3).dec()) + self.assertEqual(abs(-ddm_ex), ddm_ex) + self.assertEqual(-ddm_ex2, ddm_ex3) + self.assertEqual(ddm_ex2, abs(ddm_ex3)) + self.assertEqual(ddm_ex, dms_ex) + self.assertTrue(ddm_ex == ddm_ex) + self.assertFalse(ddm_ex == ddm_ex2) + self.assertTrue(ddm_ex != ddm_ex2) + self.assertFalse(ddm_ex != ddm_ex) + self.assertTrue(ddm_ex > ddm_ex2) + self.assertFalse(ddm_ex2 > ddm_ex) + self.assertTrue(ddm_ex2 < ddm_ex) + self.assertFalse(ddm_ex < ddm_ex2) + with self.assertRaises(TypeError): + ddm_ex * 'a' + with self.assertRaises(TypeError): + 'a' * ddm_ex + with self.assertRaises(TypeError): + ddm_ex / 'a' + with self.assertRaises(TypeError): + ddm_ex + 'a' + with self.assertRaises(TypeError): + 'a' + ddm_ex + with self.assertRaises(TypeError): + ddm_ex - 'a' + with self.assertRaises(TypeError): + 'a' - ddm_ex + + # Test reading in formatted strings + for num, ex in enumerate(ddm_strs): + self.assertEqual(DDMAngle(ex), ddm_exs[num]) + self.assertEqual(ex, str(ddm_exs[num])) + + def test_angles_interoperability(self): + self.assertEqual(DMSAngle(1, 2, 3) + DDMAngle(2, 3), DMSAngle(3, 5, 3)) + self.assertEqual(DMSAngle(3, 2, 0) - DDMAngle(2, 2.5), + DMSAngle(0, 59, 30)) + self.assertEqual(DDMAngle(2, 3) + DMSAngle(1, 2, 3), DDMAngle(3, 5.05)) + self.assertEqual(DDMAngle(3, 2) - DMSAngle(2, 2, 30), + DDMAngle(0, 59.5)) + + def test_dec2hp(self): + for num, ex in enumerate(hp_exs): + self.assertAlmostEqual(ex, dec2hp(dec_exs[num]), 13) + self.assertAlmostEqual(-ex, dec2hp(-dec_exs[num]), 13) + + def test_dec2hpa(self): + for num, ex in enumerate(dec_exs): + self.assertEqual(dec2hpa(ex), hpa_exs[num]) + self.assertEqual(dec2hpa(-ex), -hpa_exs[num]) + + def test_dec2gon(self): + for num, ex in enumerate(dec_exs): + self.assertAlmostEqual(dec2gon(ex), gon_exs[num], 13) + self.assertAlmostEqual(dec2gon(-ex), -gon_exs[num], 13) + + def test_dec2gona(self): + for num, ex in enumerate(dec_exs): + self.assertEqual(round(dec2gona(ex), 13), round(gona_exs[num], 13)) + self.assertEqual(round(dec2gona(-ex), 13), round(-gona_exs[num], 13)) + + def test_dec2dms(self): + self.assertEqual(dms_ex, dec2dms(dec_ex)) + self.assertEqual(-dms_ex, dec2dms(-dec_ex)) + + def test_dec2ddm(self): + self.assertEqual(ddm_ex, dec2ddm(dec_ex)) + self.assertEqual(-ddm_ex, dec2ddm(-dec_ex)) + + def test_hp2dec(self): + for num, ex in enumerate(dec_exs): + self.assertAlmostEqual(ex, hp2dec(hp_exs[num]), 13) + self.assertAlmostEqual(-ex, hp2dec(-hp_exs[num]), 13) + self.assertAlmostEqual(hp2dec(hp_exs[0]) + hp2dec(hp_exs[1]), + dec_exs[0] + dec_exs[1], 13) + # Test that invalid minutes and seconds components raise errors + # (Minutes and/or Seconds can't be greater than 60, therefore + # 123.718 - representing 123 degrees, 71 minutes, 80 seconds + # should raise an Error) + with self.assertRaises(ValueError): + hp2dec(123.7) + with self.assertRaises(ValueError): + hp2dec(123.318) + with self.assertRaises(ValueError): + hp2dec(123.718) + with self.assertRaises(ValueError): + hp2dec(-123.7) + with self.assertRaises(ValueError): + hp2dec(-123.318) + with self.assertRaises(ValueError): + hp2dec(-123.718) + + def test_hp2deca(self): + for num, ex in enumerate(hp_exs): + self.assertEqual(round(hp2deca(ex), 13), deca_exs[num]) + self.assertEqual(round(hp2deca(-ex), 13), -deca_exs[num]) + + def test_hp2gon(self): + for num, ex in enumerate(hp_exs): + self.assertAlmostEqual(hp2gon(ex), gon_exs[num], 13) + self.assertAlmostEqual(hp2gon(-ex), -gon_exs[num], 13) + + def test_hp2gona(self): + for num, ex in enumerate(hp_exs): + self.assertEqual(round(hp2gona(ex), 13), round(gona_exs[num], 13)) + self.assertEqual(round(hp2gona(-ex), 13), round(-gona_exs[num], 13)) + + def test_hp2rad(self): + for num, ex in enumerate(hp_exs): + self.assertEqual(hp2rad(ex), rad_exs[num]) + self.assertEqual(hp2rad(-ex), -rad_exs[num]) + + def test_hp2dms(self): + self.assertEqual(dms_ex.degree, hp2dms(hp_ex).degree) + self.assertEqual(dms_ex.minute, hp2dms(hp_ex).minute) + self.assertAlmostEqual(dms_ex.second, hp2dms(hp_ex).second, 10) + + self.assertEqual(-dms_ex, round(hp2dms(-hp_ex), 10)) + self.assertEqual(dms_ex.degree, hp2dms(-hp_ex).degree) + self.assertEqual(dms_ex.minute, hp2dms(-hp_ex).minute) + self.assertAlmostEqual(dms_ex.second, hp2dms(-hp_ex).second, 10) + + def test_hp2ddm(self): + self.assertEqual(ddm_ex, hp2ddm(hp_ex)) + self.assertEqual(-ddm_ex, hp2ddm(-hp_ex)) + + def test_gon2dec(self): + for num, ex in enumerate(gon_exs): + self.assertAlmostEqual(gon2dec(ex), dec_exs[num], 14) + self.assertAlmostEqual(gon2dec(-ex), -dec_exs[num], 14) + + def test_gon2deca(self): + for num, ex in enumerate(gon_exs): + self.assertEqual(round(gon2deca(ex), 14), deca_exs[num]) + self.assertEqual(round(gon2deca(-ex), 14), -deca_exs[num]) + + def test_gon2hp(self): + for num, ex in enumerate(gon_exs): + self.assertEqual(gon2hp(ex), hp_exs[num]) + self.assertEqual(gon2hp(-ex), -hp_exs[num]) + + def test_gon2hpa(self): + for num, ex in enumerate(gon_exs): + self.assertEqual(gon2hpa(ex), hpa_exs[num]) + self.assertEqual(gon2hpa(-ex), -hpa_exs[num]) + + def test_gon2rad(self): + for num, ex in enumerate(gon_exs): + self.assertAlmostEqual(gon2rad(ex), rad_exs[num], 15) + self.assertAlmostEqual(gon2rad(-ex), -rad_exs[num], 15) + + def test_gon2dms(self): + for num, ex in enumerate(gon_exs): + self.assertEqual(round(gon2dms(ex), 10), dms_exs[num]) + self.assertEqual(round(gon2dms(-ex), 10), -dms_exs[num]) + + def test_gon2ddm(self): + for num, ex in enumerate(gon_exs): + self.assertEqual(round(gon2ddm(ex), 12), ddm_exs[num]) + self.assertEqual(round(gon2ddm(-ex), 12), -ddm_exs[num]) + + def test_dd2sec(self): + self.assertEqual(dd2sec(1), 3600) + self.assertEqual(dd2sec(-1), -3600) + self.assertAlmostEqual(dd2sec(hp2dec(0.0001)), 1, 12) + self.assertAlmostEqual(dd2sec(hp2dec(-0.0001)), -1, 12) + self.assertAlmostEqual(dd2sec(hp2dec(0.00001)), 0.1, 12) + self.assertEqual(dd2sec(dec_ex4), 189) + self.assertEqual(dd2sec(-dec_ex4), -189) + self.assertEqual(dd2sec(dec_ex2), 45270) + self.assertEqual(dd2sec(-dec_ex2), -45270) + + def test_angular_typecheck(self): + class_exs = [deca_exs, hpa_exs, gona_exs, dms_exs, ddm_exs] + for class_ex in class_exs: + for num, ex in enumerate(class_ex): + self.assertAlmostEqual(angular_typecheck(ex), dec_exs[num], 13) + self.assertAlmostEqual(angular_typecheck(-ex), -dec_exs[num], 13) + for ex in dec_exs: + self.assertEqual(angular_typecheck(ex), ex) + self.assertEqual(angular_typecheck(-ex), -ex) + + +if __name__ == '__main__': + unittest.main() diff --git a/geodepy/tests/test_convert.py b/geodepy/tests/test_convert.py index d55fbe4..f0b68c1 100644 --- a/geodepy/tests/test_convert.py +++ b/geodepy/tests/test_convert.py @@ -8,219 +8,8 @@ yyyydoy_to_date, date_to_yyyydoy, grid2geo, hp2dec_v, geo2grid, llh2xyz, xyz2llh) -dec_ex = 123.74875 -dec_ex2 = 12.575 -dec_ex3 = -12.575 -dec_ex4 = 0.0525 -dec_ex5 = 0.005 - -hp_ex = 123.44555 -hp_ex2 = 12.3430 -hp_ex3 = -12.3430 -hp_ex4 = 0.0309 -hp_ex5 = 0.0018 - -dms_ex = DMSAngle(123, 44, 55.5) -dms_ex2 = DMSAngle(12, 34, 30) -dms_ex3 = DMSAngle(-12, -34, -30) -dms_ex4 = DMSAngle(0, 3, 9) -dms_ex5 = DMSAngle(0, 0, 18) - -ddm_ex = DDMAngle(123, 44.925) -ddm_ex2 = DDMAngle(12, 34.5) -ddm_ex3 = DDMAngle(-12, -34.5) -ddm_ex4 = DDMAngle(0, 3.15) -ddm_ex5 = DDMAngle(0, 0.3) - class TestConvert(unittest.TestCase): - def test_dec2hp(self): - self.assertAlmostEqual(hp_ex, dec2hp(dec_ex), 13) - self.assertAlmostEqual(-hp_ex, dec2hp(-dec_ex), 13) - - def test_hp2dec(self): - self.assertAlmostEqual(dec_ex, hp2dec(hp_ex), 13) - self.assertAlmostEqual(-dec_ex, hp2dec(-hp_ex), 13) - self.assertAlmostEqual(hp2dec(hp_ex) + - hp2dec(hp_ex2), dec_ex + dec_ex2, 13) - - def test_DMSAngle(self): - # Test DMSAngle Methods - self.assertEqual(dec_ex, dms_ex.dec()) - self.assertEqual(hp_ex, dms_ex.hp()) - self.assertEqual(hp_ex3, dms_ex3.hp()) - self.assertEqual(ddm_ex, dms_ex.ddm()) - self.assertEqual(-ddm_ex, -dms_ex.ddm()) - self.assertEqual(ddm_ex3, dms_ex3.ddm()) - - # Test DMSAngle Sign Conventions - self.assertEqual(-dec_ex, DMSAngle(-dms_ex.degree, dms_ex.minute, - dms_ex.second).dec()) - self.assertEqual(dec_ex, DMSAngle(dms_ex.degree, -dms_ex.minute, - -dms_ex.second).dec()) - self.assertAlmostEqual(-dec_ex4, DMSAngle(0, -dms_ex4.minute, - dms_ex4.second).dec(), 9) - self.assertAlmostEqual(dec_ex4, DMSAngle(0, dms_ex4.minute, - dms_ex4.second).dec(), 9) - self.assertEqual(-dec_ex5, DMSAngle(0, 0, -dms_ex5.second).dec()) - self.assertEqual(dec_ex5, DMSAngle(0, 0, dms_ex5.second).dec()) - self.assertEqual(-dms_ex3, DMSAngle(12, 34, -30)) - self.assertEqual(dms_ex.sign, 1) - self.assertEqual(-dms_ex.sign, -1) - self.assertEqual(dms_ex4.sign, 1) - self.assertEqual(-dms_ex4.sign, -1) - self.assertEqual(dms_ex5.sign, 1) - self.assertEqual(-dms_ex5.sign, -1) - self.assertEqual(DMSAngle(-1, 2, 3).sign, -1) - self.assertEqual(DMSAngle(1, -2, 3).sign, 1) - self.assertEqual(DMSAngle(1, 2, -3).sign, 1) - self.assertEqual(DMSAngle(0, -1, 2).sign, -1) - self.assertEqual(DMSAngle(0, 0, -3).sign, -1) - self.assertEqual(DMSAngle(-0, 1, 2).sign, 1) - self.assertEqual(DMSAngle(-0.0, 1, 2).sign, -1) - self.assertEqual(repr(dms_ex), '{DMSAngle: +123d 44m 55.5s}') - self.assertEqual(repr(dms_ex3), '{DMSAngle: -12d 34m 30s}') - - # Test DMSAngle Overloads - self.assertEqual(dec_ex + dec_ex2, (dms_ex + dms_ex2).dec()) - self.assertEqual(dec_ex2 + dec_ex, (dms_ex2 + dms_ex).dec()) - self.assertEqual(dec_ex - dec_ex2, (dms_ex - dms_ex2).dec()) - self.assertEqual(dec_ex2 - dec_ex, (dms_ex2 - dms_ex).dec()) - self.assertEqual(dec_ex * 5, (dms_ex * 5).dec()) - self.assertEqual(5 * dec_ex, (5 * dms_ex).dec()) - self.assertEqual(dec_ex / 3, (dms_ex / 3).dec()) - self.assertEqual(abs(-dms_ex), dms_ex) - self.assertEqual(-dms_ex2, dms_ex3) - self.assertEqual(dms_ex2, abs(dms_ex3)) - self.assertEqual(dms_ex, ddm_ex) - self.assertTrue(dms_ex == dms_ex) - self.assertFalse(dms_ex == dms_ex2) - self.assertTrue(dms_ex != dms_ex2) - self.assertFalse(dms_ex != dms_ex) - self.assertTrue(dms_ex > dms_ex2) - self.assertFalse(dms_ex2 > dms_ex) - self.assertTrue(dms_ex2 < dms_ex) - self.assertFalse(dms_ex < dms_ex2) - with self.assertRaises(TypeError): - dms_ex * 'a' - with self.assertRaises(TypeError): - 'a' * dms_ex - with self.assertRaises(TypeError): - dms_ex / 'a' - with self.assertRaises(TypeError): - dms_ex + 'a' - with self.assertRaises(TypeError): - 'a' + dms_ex - with self.assertRaises(TypeError): - dms_ex - 'a' - with self.assertRaises(TypeError): - 'a' - dms_ex - - # Test Class Interoperability - self.assertEqual(DMSAngle(1, 2, 3) + DDMAngle(2, 3), DMSAngle(3, 5, 3)) - self.assertEqual(DMSAngle(3, 2, 0) - DDMAngle(2, 2.5), - DMSAngle(0, 59, 30)) - self.assertEqual(DDMAngle(2, 3) + DMSAngle(1, 2, 3), DDMAngle(3, 5.05)) - self.assertEqual(DDMAngle(3, 2) - DMSAngle(2, 2, 30), - DDMAngle(0, 59.5)) - - def test_DDMAngle(self): - # Test DDMAngle Methods - self.assertEqual(dec_ex, ddm_ex.dec()) - self.assertEqual(hp_ex, ddm_ex.hp()) - self.assertEqual(dms_ex, ddm_ex.dms()) - self.assertEqual(hp_ex3, ddm_ex3.hp()) - - # Test DMSAngle Sign Conventions - self.assertEqual(-dec_ex, DDMAngle(-dms_ex.degree, - ddm_ex.minute).dec()) - self.assertEqual(dec_ex, DDMAngle(dms_ex.degree, -ddm_ex.minute).dec()) - self.assertAlmostEqual(-dec_ex4, DDMAngle(0, -ddm_ex4.minute).dec(), 9) - self.assertAlmostEqual(dec_ex4, DDMAngle(0, ddm_ex4.minute).dec(), 9) - self.assertEqual(-ddm_ex3, DDMAngle(12, 34.5)) - self.assertEqual(ddm_ex.sign, 1) - self.assertEqual(-ddm_ex.sign, -1) - self.assertEqual(ddm_ex4.sign, 1) - self.assertEqual(-ddm_ex4.sign, -1) - self.assertEqual(ddm_ex5.sign, 1) - self.assertEqual(-ddm_ex5.sign, -1) - self.assertEqual(DDMAngle(-1, 2).sign, -1) - self.assertEqual(DDMAngle(1, -2).sign, 1) - self.assertEqual(DDMAngle(1, 2).sign, 1) - self.assertEqual(DDMAngle(0, -1).sign, -1) - self.assertEqual(DDMAngle(-0, 1).sign, 1) - self.assertEqual(DDMAngle(-0.0, 1).sign, -1) - self.assertEqual(repr(ddm_ex), '{DDMAngle: +123d 44.925m}') - self.assertEqual(repr(ddm_ex3), '{DDMAngle: -12d 34.5m}') - - # Test DDMAngle Overloads - self.assertEqual(dec_ex + dec_ex2, (ddm_ex + ddm_ex2).dec()) - self.assertEqual(dec_ex2 + dec_ex, (ddm_ex2 + ddm_ex).dec()) - self.assertEqual(dec_ex - dec_ex2, (ddm_ex - ddm_ex2).dec()) - self.assertEqual(dec_ex2 - dec_ex, (ddm_ex2 - ddm_ex).dec()) - self.assertEqual(dec_ex * 5, (ddm_ex * 5).dec()) - self.assertEqual(5 * dec_ex, (5 * ddm_ex).dec()) - self.assertEqual(dec_ex / 3, (ddm_ex / 3).dec()) - self.assertEqual(abs(-ddm_ex), ddm_ex) - self.assertEqual(-ddm_ex2, ddm_ex3) - self.assertEqual(ddm_ex2, abs(ddm_ex3)) - self.assertEqual(ddm_ex, dms_ex) - self.assertTrue(ddm_ex == ddm_ex) - self.assertFalse(ddm_ex == ddm_ex2) - self.assertTrue(ddm_ex != ddm_ex2) - self.assertFalse(ddm_ex != ddm_ex) - self.assertTrue(ddm_ex > ddm_ex2) - self.assertFalse(ddm_ex2 > ddm_ex) - self.assertTrue(ddm_ex2 < ddm_ex) - self.assertFalse(ddm_ex < ddm_ex2) - with self.assertRaises(TypeError): - ddm_ex * 'a' - with self.assertRaises(TypeError): - 'a' * ddm_ex - with self.assertRaises(TypeError): - ddm_ex / 'a' - with self.assertRaises(TypeError): - ddm_ex + 'a' - with self.assertRaises(TypeError): - 'a' + ddm_ex - with self.assertRaises(TypeError): - ddm_ex - 'a' - with self.assertRaises(TypeError): - 'a' - ddm_ex - - def test_dec2dms(self): - self.assertEqual(dms_ex, dec2dms(dec_ex)) - self.assertEqual(-dms_ex, dec2dms(-dec_ex)) - - def test_dec2ddm(self): - self.assertEqual(ddm_ex, dec2ddm(dec_ex)) - self.assertEqual(-ddm_ex, dec2ddm(-dec_ex)) - - def test_hp2dms(self): - self.assertEqual(dms_ex.degree, hp2dms(hp_ex).degree) - self.assertEqual(dms_ex.minute, hp2dms(hp_ex).minute) - self.assertAlmostEqual(dms_ex.second, hp2dms(hp_ex).second, 10) - - self.assertEqual(-dms_ex.sign, hp2dms(-hp_ex).sign) - self.assertEqual(dms_ex.degree, hp2dms(-hp_ex).degree) - self.assertEqual(dms_ex.minute, hp2dms(-hp_ex).minute) - self.assertAlmostEqual(dms_ex.second, hp2dms(-hp_ex).second, 10) - - def test_hp2ddm(self): - self.assertEqual(ddm_ex, hp2ddm(hp_ex)) - self.assertEqual(-ddm_ex, hp2ddm(-hp_ex)) - - def test_dd2sec(self): - self.assertEqual(dd2sec(1), 3600) - self.assertEqual(dd2sec(-1), -3600) - self.assertEqual(dd2sec(hp2dec(0.0001)), 1) - self.assertEqual(dd2sec(hp2dec(-0.0001)), -1) - self.assertEqual(dd2sec(hp2dec(0.00001)), 0.1) - self.assertEqual(dd2sec(dec_ex4), 189) - self.assertEqual(dd2sec(-dec_ex4), -189) - self.assertEqual(dd2sec(dec_ex2), 45270) - self.assertEqual(dd2sec(-dec_ex2), -45270) - def test_date_to_yyyydoy(self): self.assertEqual(date_to_yyyydoy(datetime.date(2020, 1, 4)), '2020.004')