Skip to content

Instantly share code, notes, and snippets.

@haxwithaxe
Last active June 18, 2025 14:50
Show Gist options
  • Save haxwithaxe/8441769f0cca2ff27d26ba6386e34386 to your computer and use it in GitHub Desktop.
Save haxwithaxe/8441769f0cca2ff27d26ba6386e34386 to your computer and use it in GitHub Desktop.
Tools I've made for myself for playing around with learning about celestial navigation. Updates as I progress.
"""Tools for playing with celestial navigation."""
import math
import re
from typing import Union
ANGLE_HOUR = 15 # degrees/hour
_DECIMAL_RE = r'\d+(?:\.\d+)?'
_DEG_MIN_SEC_RE = re.compile(
r'(?P<degrees>-?'
+ _DECIMAL_RE
+ ')(?:d|deg)(?:(?P<minutes>'
+ _DECIMAL_RE
+ ''')['m])?(?:(?P<seconds>'''
+ _DECIMAL_RE
+ ')["s])?', # nofmt
)
def interpolate3(x_d: float, y1: float, y2: float, y3: float) -> float:
"""Interpolate from 3 points.
Arguments:
x_d: ("x sub delta") the difference between x2 (corresponding to y2)
and the target value.
y1: The tabular value before the center value.
y2: The center tabular value.
y3: The tabular value after the center value.
"""
assert x_d > -1
assert x_d < 1
a = y2 - y1
b = y3 - y2
c = b - a
return y2 + x_d / 2 * (a + b + x_d * c)
# Alternatively the one line version.
# return y2 + x_d / 2 * ((y2 - y1) + (y3 - y2) + x_d * (y1 + y3 - 2 * y2))
def interpolate5(
x_d: float, y1: float, y2: float, y3: float, y4: float, y5: float
) -> float:
"""Interpolate from 5 points.
Arguments:
x_d: ("x sub delta") the difference between x3 (corresponding to y3)
and the target value.
y1: The first tabular value before the center value.
y2: The second tabular value before the center value.
y3: The center tabular value.
y4: The first tabular value after the center value.
y5: The second tabular value after the center value.
"""
assert x_d > -1
assert x_d < 1
# First pass
a = y2 - y1
b = y3 - y2
c = y4 - y3
d = y5 - y4
# Second pass
e = b - a
f = c - b
g = d - c
# Third pass
h = f - e
j = g - f
# Fourth pass
k = j - h
return (
y3
+ x_d * ((b + c) / 2 - (h + j) / 12)
+ x_d**2 * (f / 2 - k / 24)
+ x_d**3 * ((h + j) / 12)
+ x_d**4 * (k / 24) # nofmt
)
def interpolate5_n_0(
n_0: float, y1: float, y2: float, y3: float, y4: float, y5: float
) -> float:
"""Interpolate n_0 from 5 points.
Arguments:
n_0: ("n sub 0") the difference between x3 (corresponding to y3)
and the target value.
y1: The first tabular value before the center value.
y2: The second tabular value before the center value.
y3: The center tabular value.
y4: The first tabular value after the center value.
y5: The second tabular value after the center value.
"""
assert n_0 > -1
assert n_0 < 1
# First pass
a = y2 - y1
b = y3 - y2
c = y4 - y3
d = y5 - y4
# Second pass
e = b - a
f = c - b
g = d - c
# Third pass
h = f - e
j = g - f
# Fourth pass
k = j - h
return (
-24 * y3 + n_0**2 * (k - 12 * f) - 2 * n_0**3 * (h + j) - n_0**4 * k
) / ( # nofmt
2 * (6 * b + 6 * c - h - j)
) # nofmt
def time_hr_min_sec_to_hr(
hours: int = 0,
minutes: int = 0,
seconds: Union[float, int] = 0,
) -> float:
"""Convert time in hours, minutes, seconds to decimal hours."""
return hours + (minutes / 60) + (seconds / 60 / 60)
def time_hr_to_deg(hours: float) -> float:
"""Convert decimal hours to degrees."""
return hours * ANGLE_HOUR
def norm_deg_min_sec(deg_min_sec: str) -> tuple[int, int, float]:
"""Normalize a DMS string.
Accounts for negative DMS.
"""
found = _DEG_MIN_SEC_RE.match(deg_min_sec)
degrees = float(found.groupdict().get('degrees', 0))
minutes = float(found.groupdict().get('minutes', 0))
seconds = float(found.groupdict().get('seconds', 0))
if degrees >= 0:
return (degrees, minutes, seconds)
# Angle is negative. Ensure all values are negative
return (degrees, 0 - abs(minutes), 0 - abs(seconds))
def deg_min_sec_to_degrees(
degrees: Union[float, int],
minutes: Union[float, int] = 0,
seconds: Union[float, int] = 0,
) -> float:
"""Convert DMS to decimal degrees.
Arguments:
degrees:
minutes (optional): Defaults to ``0``.
seconds (optional): Defaults to ``0``.
Returns:
`angle` in decimal degrees.
"""
degrees += minutes / 60
degrees += seconds / 60 / 60
return degrees
dmstd = deg_min_sec_to_degrees
def degrees_to_deg_min_sec(
angle: Union['Angle', float, int],
) -> tuple[int, int, float]:
"""Convert decimal degrees to DMS.
Arguments:
angle: Angle in decimal degrees.
Returns:
A 3-tuple of `(degrees, minutes, seconds)`.
"""
degrees = int(angle)
decimal = angle - degrees
minutes = int(decimal * 60)
seconds = ((decimal * 60) - minutes) * 60
return (degrees, minutes, float(seconds))
dtdms = degrees_to_deg_min_sec
def degrees_to_deg_min(angle: Union['Angle', float, int]) -> tuple[int, float]:
"""Convert decimal degrees to DMS.
Arguments:
angle: Angle in decimal degrees.
Returns:
A 3-tuple of `(degrees, minutes, seconds)`.
"""
degrees = int(angle)
decimal = angle - degrees
minutes = decimal * 60
return (degrees, float(minutes))
# Decorators
def _any_to_angle(func: callable) -> callable:
"""Take any number or `Angle` as input and return an `Angle` as output."""
def wrapper(slf: 'Angle', other: Union['Angle', float, int]) -> 'Angle':
if isinstance(other, slf.__class__.__bases__[0]):
other = other.radians
return slf.__class__(func(slf, other))
return wrapper
def _any_to_bool(func: callable) -> callable:
"""Take any number or `Angle` as input and return a `bool`."""
def wrapper(slf: 'Angle', other: Union['Angle', float, int]) -> 'Angle':
if isinstance(other, slf.__class__.__bases__[0]):
other = other.radians
return func(slf, other)
return wrapper
def _as_angle(func: callable) -> callable:
"""Cast the output as an `Angle`."""
def wrapper(slf: 'Angle') -> 'Angle':
return slf.__class__(func(slf))
return wrapper
class Angle:
"""Base class for angle types.
The goal is to preserve unit info as much as possible and avoid converting
unnecessarily.
Arguments:
value: An angle in degrees or radians.
Attributes:
degrees: The angle in degrees.
dms: The angle in degrees, minutes, seconds.
radians: The angle in radians.
"""
def __init__( # noqa: D107
self,
value: Union['Angle', float, int],
) -> None:
self.value = float(value)
@property
def degrees(self) -> 'Angle': # noqa: D102
raise NotImplementedError()
@degrees.setter
def degrees(self, value: Union[float, int, str]) -> None: # noqa: D102
raise NotImplementedError()
@property
def dms(self) -> tuple[float, float, float]: # noqa: D102
return degrees_to_deg_min_sec(self.degrees)
@dms.setter
def dms(self, value: Union[str, tuple]) -> None: # noqa: D102
self.degrees = value
@property
def dm(self) -> tuple[float, float, float]: # noqa: D102
return degrees_to_deg_min_sec(self.degrees)
@dm.setter
def dm(self, value: Union[str, tuple]) -> None: # noqa: D102
self.degrees = value
@property
def radians(self) -> 'Angle': # noqa: D102
raise NotImplementedError()
@radians.setter
def radians(self, value: Union[float, int]) -> None: # noqa: D102
raise NotImplementedError()
def acos(self) -> float:
"""Arc cosine of the angle."""
return math.acos(float(self.radians))
def acosh(self) -> float:
"""Hyperbolic arc cosine of the angle."""
return math.acosh(float(self.radians))
def asin(self) -> float:
"""Arc sine of the angle."""
return math.asin(float(self.radians))
def asinh(self) -> float:
"""Hyperbolic arc sine of the angle."""
return math.asinh(float(self.radians))
def atan(self) -> float:
"""Arc tangent of the angle."""
return math.atan(float(self.radians))
def atanh(self) -> float:
"""Hyperbolic arc tangent of the angle."""
return math.atanh(float(self.radians))
def cos(self) -> float:
"""Cosine of the angle."""
return math.cos(float(self.radians))
def cosh(self) -> float:
"""Hyperbolic cosine of the angle."""
return math.cosh(float(self.radians))
def sin(self) -> float:
"""Sine of the angle."""
return math.sin(float(self.radians))
def sinh(self) -> float:
"""Hyperbolic sine of the angle."""
return math.sinh(float(self.radians))
def tan(self) -> float:
"""Tangent of the angle."""
return math.tan(float(self.radians))
def tanh(self) -> float:
"""Hyperbolic tangent of the angle."""
return math.tanh(float(self.radians))
@_any_to_angle
def __add__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return self.value + other
@_any_to_angle
def __mul__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__mul__(self.value, other)
@_as_angle
def __abs__(self) -> 'Angle': # noqa: D105
return float.__abs__(self.value)
@_any_to_angle
def __div__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__div__(self.value, other)
@_any_to_angle
def __divmod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__divmod__(self.value, other)
@_any_to_bool
def __eq__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> bool:
return float.__eq__(self.value, other)
@_as_angle
def __floor__(self) -> 'Angle': # noqa: D105
return float.__floor__(self.value)
@_any_to_angle
def __floordiv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__floordiv__(self.value, other)
@_any_to_bool
def __ge__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__ge__(self.value, other)
@_any_to_bool
def __gt__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__gt__(self.value, other)
@_any_to_bool
def __le__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__le__(self.value, other)
@_any_to_bool
def __lt__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__lt__(self.value, other)
@_any_to_angle
def __mod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__mod__(self.value, other)
@_any_to_bool
def __ne__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return self.value != other
@_as_angle
def __neg__(self) -> 'Angle': # noqa: D105
return float.__neg__(self.value)
@_as_angle
def __pos__(self) -> 'Angle': # noqa: D105
return float.__pos__(self.value)
@_any_to_angle
def __pow__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__pow__(self.value, other)
@_any_to_angle
def __radd__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__radd__(self.value, other)
@_any_to_angle
def __rdivmod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rdivmod__(self.value, other)
@_any_to_angle
def __reduce__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__reduce__(self.value, other)
@_any_to_angle
def __reduce_ex__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__reduce_ex__(self.value, other)
@_any_to_angle
def __rfloordiv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rfloordiv__(self.value, other)
@_any_to_angle
def __rmod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rmod__(self.value, other)
@_any_to_angle
def __rmul__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rmul__(self.value, other)
@_as_angle
def __round__(self) -> 'Angle': # noqa: D105
return round(self.value)
@_any_to_angle
def __rpow__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rpow__(self.value, other)
@_any_to_angle
def __rsub__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rsub__(self.value, other)
@_any_to_angle
def __rtruediv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rtruediv__(self.value, other)
@_any_to_angle
def __sub__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__sub__(self.value, other)
@_any_to_angle
def __truediv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__truediv__(self.value, other)
@_as_angle
def __trunc__(self) -> 'Angle': # noqa: D105
return float.__trunc__(self.value)
def __int__(self) -> int: # noqa: D105
return int(self.value)
def __float__(self) -> float: # noqa: D105
return float(self.value)
def __repr__(self) -> str: # noqa: D105
return (
f'<{self.__class__.__name__} radians={float(self.radians)}, '
f'degrees={float(self.degrees)}>'
)
def __str__(self) -> str: # noqa: D105
raise NotImplementedError()
class Degree(Angle):
"""An angle in degrees.
Arguments:
degrees: The angle in degrees (decimal or DMS).
"""
def __init__( # noqa: D107
self,
degrees: Union['Angle', float, int, str, tuple],
):
if isinstance(degrees, Angle):
degrees = degrees.degrees
elif isinstance(degrees, str):
degrees = deg_min_sec_to_degrees(*norm_deg_min_sec(degrees))
elif isinstance(degrees, tuple):
degrees = deg_min_sec_to_degrees(*degrees)
super().__init__(float(degrees))
@property
def degrees(self) -> 'Degree': # noqa: D102
return Degree(self.value)
@degrees.setter
def degrees( # noqa: D102
self,
value_deg: Union[Angle, float, int, str, tuple],
) -> None:
if isinstance(value_deg, Angle):
value_deg = value_deg.degrees
elif isinstance(value_deg, str):
value_deg = deg_min_sec_to_degrees(*norm_deg_min_sec(value_deg))
elif isinstance(value_deg, tuple):
value_deg = deg_min_sec_to_degrees(*value_deg)
self.value = float(value_deg)
@property
def radians(self) -> 'Radian': # noqa: D102
return Radian(math.radians(self.value))
@radians.setter
def radians(self, value_rad: Union[float, int]) -> None: # noqa: D102
self.value = math.degrees(value_rad)
def __str__(self) -> str: # noqa: D105
return f'{float(self.degrees)}deg'
class Radian(Angle):
"""An angle in radians.
Arguments:
radians: The angle in radians.
"""
def __init__(self, radians: Union['Angle', float, int]): # noqa: D107
if isinstance(radians, Angle):
radians = float(radians.radians)
super().__init__(radians)
@property
def degrees(self) -> Degree: # noqa: D102
return Degree(math.degrees(self.value))
@degrees.setter
def degrees( # noqa: D102
self,
value_deg: Union[Angle, float, int, str, tuple],
) -> None:
if isinstance(value_deg, (str, tuple)):
value_deg = Degree(value_deg).degrees
elif isinstance(value_deg, Angle):
value_deg = value_deg.degrees
self.value = math.radians(value_deg)
@property
def radians(self) -> 'Radian': # noqa: D102
return self.value
@radians.setter
def radians(
self,
value_rad: Union[Angle, float, int],
) -> None: # noqa: D102
if isinstance(value_rad, Angle):
value_rad = float(value_rad.radians)
self.value = value_rad
def __str__(self) -> str: # noqa: D105
return f'{float(self.radians)}rad'
"""Tools for playing with celestial navigation."""
import math
import re
from typing import Union
ANGLE_HOUR = 15 # degrees/hour
_DECIMAL_RE = r'\d+(?:\.\d+)?'
_DEG_MIN_SEC_RE = re.compile(
r'(?P<degrees>-?'
+ _DECIMAL_RE
+ ')(?:d|deg)(?:(?P<minutes>'
+ _DECIMAL_RE
+ ''')['m])?(?:(?P<seconds>'''
+ _DECIMAL_RE
+ ')["s])?', # nofmt
)
def interpolate3(x_d: float, y1: float, y2: float, y3: float) -> float:
"""Interpolate from 3 points.
Arguments:
x_d: ("x sub delta") the difference between x2 (corresponding to y2)
and the target value.
y1: The tabular value before the center value.
y2: The center tabular value.
y3: The tabular value after the center value.
"""
assert x_d > -1
assert x_d < 1
a = y2 - y1
b = y3 - y2
c = b - a
return y2 + x_d / 2 * (a + b + x_d * c)
# Alternatively the one line version.
# return y2 + x_d / 2 * ((y2 - y1) + (y3 - y2) + x_d * (y1 + y3 - 2 * y2))
def interpolate5(
x_d: float, y1: float, y2: float, y3: float, y4: float, y5: float
) -> float:
"""Interpolate from 3 points.
Arguments:
x_d: ("x sub delta") the difference between x3 (corresponding to y3)
and the target value.
y1: The tabular value before the center value.
y2: The center tabular value.
y3: The tabular value after the center value.
"""
assert x_d > -1
assert x_d < 1
# First pass
a = y2 - y1
b = y3 - y2
c = y4 - y3
d = y5 - y4
# Second pass
e = b - a
f = c - b
g = d - c
# Third pass
h = f - e
j = g - f
# Fourth pass
k = j - h
return (
y3
+ x_d * ((b + c) / 2 - (h + j) / 12)
+ x_d**2 * (f / 2 - k / 24)
+ x_d**3 * ((h + j) / 12)
+ x_d**4 * (k / 24) # nofmt
)
def time_hr_min_sec_to_hr(
hours: int = 0,
minutes: int = 0,
seconds: Union[float, int] = 0,
) -> float:
"""Convert time in hours, minutes, seconds to decimal hours."""
return hours + (minutes / 60) + (seconds / 60 / 60)
def time_hr_to_deg(hours: float) -> float:
"""Convert decimal hours to degrees."""
return hours * ANGLE_HOUR
def norm_deg_min_sec(deg_min_sec: str) -> tuple[int, int, float]:
"""Normalize a DMS string.
Accounts for negative DMS.
"""
found = _DEG_MIN_SEC_RE.match(deg_min_sec)
degrees = float(found.groupdict().get('degrees', 0))
minutes = float(found.groupdict().get('minutes', 0))
seconds = float(found.groupdict().get('seconds', 0))
if degrees >= 0:
return (degrees, minutes, seconds)
# Angle is negative. Ensure all values are negative
return (degrees, 0 - abs(minutes), 0 - abs(seconds))
def deg_min_sec_to_degrees(
degrees: Union[float, int],
minutes: Union[float, int] = 0,
seconds: Union[float, int] = 0,
) -> float:
"""Convert DMS to decimal degrees.
Arguments:
degrees:
minutes (optional): Defaults to ``0``.
seconds (optional): Defaults to ``0``.
Returns:
`angle` in decimal degrees.
"""
degrees += minutes / 60
degrees += seconds / 60 / 60
return degrees
dmstd = deg_min_sec_to_degrees
def degrees_to_deg_min_sec(
angle: Union['Angle', float, int],
) -> tuple[int, int, float]:
"""Convert decimal degrees to DMS.
Arguments:
angle: Angle in decimal degrees.
Returns:
A 3-tuple of `(degrees, minutes, seconds)`.
"""
degrees = int(angle)
decimal = angle - degrees
minutes = int(decimal * 60)
seconds = ((decimal * 60) - minutes) * 60
return (degrees, minutes, float(seconds))
dtdms = degrees_to_deg_min_sec
def degrees_to_deg_min(angle: Union['Angle', float, int]) -> tuple[int, float]:
"""Convert decimal degrees to DMS.
Arguments:
angle: Angle in decimal degrees.
Returns:
A 3-tuple of `(degrees, minutes, seconds)`.
"""
degrees = int(angle)
decimal = angle - degrees
minutes = decimal * 60
return (degrees, float(minutes))
# Decorators
def _any_to_angle(func: callable) -> callable:
"""Take any number or `Angle` as input and return an `Angle` as output."""
def wrapper(slf: 'Angle', other: Union['Angle', float, int]) -> 'Angle':
if isinstance(other, slf.__class__.__bases__[0]):
other = other.radians
return slf.__class__(func(slf, other))
return wrapper
def _any_to_bool(func: callable) -> callable:
"""Take any number or `Angle` as input and return a `bool`."""
def wrapper(slf: 'Angle', other: Union['Angle', float, int]) -> 'Angle':
if isinstance(other, slf.__class__.__bases__[0]):
other = other.radians
return func(slf, other)
return wrapper
def _as_angle(func: callable) -> callable:
"""Cast the output as an `Angle`."""
def wrapper(slf: 'Angle') -> 'Angle':
return slf.__class__(func(slf))
return wrapper
class Angle:
"""Base class for angle types.
The goal is to preserve unit info as much as possible and avoid converting
unnecessarily.
Arguments:
value: An angle in degrees or radians.
Attributes:
degrees: The angle in degrees.
dms: The angle in degrees, minutes, seconds.
radians: The angle in radians.
"""
def __init__( # noqa: D107
self,
value: Union['Angle', float, int],
) -> None:
self.value = float(value)
@property
def degrees(self) -> 'Angle': # noqa: D102
raise NotImplementedError()
@degrees.setter
def degrees(self, value: Union[float, int, str]) -> None: # noqa: D102
raise NotImplementedError()
@property
def dms(self) -> tuple[float, float, float]: # noqa: D102
return degrees_to_deg_min_sec(self.degrees)
@dms.setter
def dms(self, value: Union[str, tuple]) -> None: # noqa: D102
self.degrees = value
@property
def dm(self) -> tuple[float, float, float]: # noqa: D102
return degrees_to_deg_min_sec(self.degrees)
@dm.setter
def dm(self, value: Union[str, tuple]) -> None: # noqa: D102
self.degrees = value
@property
def radians(self) -> 'Angle': # noqa: D102
raise NotImplementedError()
@radians.setter
def radians(self, value: Union[float, int]) -> None: # noqa: D102
raise NotImplementedError()
def acos(self) -> float:
"""Arc cosine of the angle."""
return math.acos(float(self.radians))
def acosh(self) -> float:
"""Hyperbolic arc cosine of the angle."""
return math.acosh(float(self.radians))
def asin(self) -> float:
"""Arc sine of the angle."""
return math.asin(float(self.radians))
def asinh(self) -> float:
"""Hyperbolic arc sine of the angle."""
return math.asinh(float(self.radians))
def atan(self) -> float:
"""Arc tangent of the angle."""
return math.atan(float(self.radians))
def atanh(self) -> float:
"""Hyperbolic arc tangent of the angle."""
return math.atanh(float(self.radians))
def cos(self) -> float:
"""Cosine of the angle."""
return math.cos(float(self.radians))
def cosh(self) -> float:
"""Hyperbolic cosine of the angle."""
return math.cosh(float(self.radians))
def sin(self) -> float:
"""Sine of the angle."""
return math.sin(float(self.radians))
def sinh(self) -> float:
"""Hyperbolic sine of the angle."""
return math.sinh(float(self.radians))
def tan(self) -> float:
"""Tangent of the angle."""
return math.tan(float(self.radians))
def tanh(self) -> float:
"""Hyperbolic tangent of the angle."""
return math.tanh(float(self.radians))
@_any_to_angle
def __add__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return self.value + other
@_any_to_angle
def __mul__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__mul__(self.value, other)
@_as_angle
def __abs__(self) -> 'Angle': # noqa: D105
return float.__abs__(self.value)
@_any_to_angle
def __div__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__div__(self.value, other)
@_any_to_angle
def __divmod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__divmod__(self.value, other)
@_any_to_bool
def __eq__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> bool:
return float.__eq__(self.value, other)
@_as_angle
def __floor__(self) -> 'Angle': # noqa: D105
return float.__floor__(self.value)
@_any_to_angle
def __floordiv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__floordiv__(self.value, other)
@_any_to_bool
def __ge__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__ge__(self.value, other)
@_any_to_bool
def __gt__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__gt__(self.value, other)
@_any_to_bool
def __le__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__le__(self.value, other)
@_any_to_bool
def __lt__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return float.__lt__(self.value, other)
@_any_to_angle
def __mod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__mod__(self.value, other)
@_any_to_bool
def __ne__(self, other: Union['Angle', float, int]) -> bool: # noqa: D105
return self.value != other
@_as_angle
def __neg__(self) -> 'Angle': # noqa: D105
return float.__neg__(self.value)
@_as_angle
def __pos__(self) -> 'Angle': # noqa: D105
return float.__pos__(self.value)
@_any_to_angle
def __pow__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__pow__(self.value, other)
@_any_to_angle
def __radd__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__radd__(self.value, other)
@_any_to_angle
def __rdivmod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rdivmod__(self.value, other)
@_any_to_angle
def __reduce__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__reduce__(self.value, other)
@_any_to_angle
def __reduce_ex__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__reduce_ex__(self.value, other)
@_any_to_angle
def __rfloordiv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rfloordiv__(self.value, other)
@_any_to_angle
def __rmod__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rmod__(self.value, other)
@_any_to_angle
def __rmul__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rmul__(self.value, other)
@_as_angle
def __round__(self) -> 'Angle': # noqa: D105
return round(self.value)
@_any_to_angle
def __rpow__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rpow__(self.value, other)
@_any_to_angle
def __rsub__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rsub__(self.value, other)
@_any_to_angle
def __rtruediv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__rtruediv__(self.value, other)
@_any_to_angle
def __sub__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__sub__(self.value, other)
@_any_to_angle
def __truediv__( # noqa: D105
self,
other: Union['Angle', float, int],
) -> 'Angle':
return float.__truediv__(self.value, other)
@_as_angle
def __trunc__(self) -> 'Angle': # noqa: D105
return float.__trunc__(self.value)
def __int__(self) -> int: # noqa: D105
return int(self.value)
def __float__(self) -> float: # noqa: D105
return float(self.value)
def __repr__(self) -> str: # noqa: D105
return (
f'<{self.__class__.__name__} radians={float(self.radians)}, '
f'degrees={float(self.degrees)}>'
)
def __str__(self) -> str: # noqa: D105
raise NotImplementedError()
class Degree(Angle):
"""An angle in degrees.
Arguments:
degrees: The angle in degrees (decimal or DMS).
"""
def __init__( # noqa: D107
self,
degrees: Union['Angle', float, int, str, tuple],
):
if isinstance(degrees, Angle):
degrees = degrees.degrees
elif isinstance(degrees, str):
degrees = deg_min_sec_to_degrees(*norm_deg_min_sec(degrees))
elif isinstance(degrees, tuple):
degrees = deg_min_sec_to_degrees(*degrees)
super().__init__(float(degrees))
@property
def degrees(self) -> 'Degree': # noqa: D102
return Degree(self.value)
@degrees.setter
def degrees( # noqa: D102
self,
value_deg: Union[Angle, float, int, str, tuple],
) -> None:
if isinstance(value_deg, Angle):
value_deg = value_deg.degrees
elif isinstance(value_deg, str):
value_deg = deg_min_sec_to_degrees(*norm_deg_min_sec(value_deg))
elif isinstance(value_deg, tuple):
value_deg = deg_min_sec_to_degrees(*value_deg)
self.value = float(value_deg)
@property
def radians(self) -> 'Radian': # noqa: D102
return Radian(math.radians(self.value))
@radians.setter
def radians(self, value_rad: Union[float, int]) -> None: # noqa: D102
self.value = math.degrees(value_rad)
def __str__(self) -> str: # noqa: D105
return f'{float(self.degrees)}deg'
class Radian(Angle):
"""An angle in radians.
Arguments:
radians: The angle in radians.
"""
def __init__(self, radians: Union['Angle', float, int]): # noqa: D107
if isinstance(radians, Angle):
radians = float(radians.radians)
super().__init__(radians)
@property
def degrees(self) -> Degree: # noqa: D102
return Degree(math.degrees(self.value))
@degrees.setter
def degrees( # noqa: D102
self,
value_deg: Union[Angle, float, int, str, tuple],
) -> None:
if isinstance(value_deg, (str, tuple)):
value_deg = Degree(value_deg).degrees
elif isinstance(value_deg, Angle):
value_deg = value_deg.degrees
self.value = math.radians(value_deg)
@property
def radians(self) -> 'Radian': # noqa: D102
return self.value
@radians.setter
def radians(
self,
value_rad: Union[Angle, float, int],
) -> None: # noqa: D102
if isinstance(value_rad, Angle):
value_rad = float(value_rad.radians)
self.value = value_rad
def __str__(self) -> str: # noqa: D105
return f'{float(self.radians)}rad'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment