-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathangle.py
188 lines (159 loc) · 5.7 KB
/
angle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
from math import pi, degrees, radians, fabs, modf, copysign
from mymath import pfmod
import sys
from numbers import Number
DEFAULT_ENCODING = 'UTF-8'
"""
Angle class that can accumulate an angle value in radians
"""
class Angle:
""" Create an Angle in radians """
def __init__(self, val_in_rad):
assert isinstance(val_in_rad, Number), 'Radians should be a Number'
self.rads = val_in_rad
self.canonical()
def __add__(self, other):
assert isinstance(other, Angle), 'Unsupported type for addition'
return Angle(self.rads + other.rads)
def __iadd__(self,other):
assert isinstance(other, Angle), 'Unsupported type for i-addition'
self.rads += other.rads
self.canonical()
return self
def __sub__(self,other):
assert isinstance(other, Angle), 'Unsupported type for subtraction'
return Angle(self.rads - other.rads)
def __isub__(self,other):
assert isinstance(other, Angle), 'Unsupported type for i-subtraction'
self.rads -= other.rads
self.canonical()
return self
def __get_sign_str(self):
sign = copysign(1,self.rads) # 1 or -1
if (self.rads == 0): signstr = u' '
elif (sign < 0): signstr = u'-'
else: signstr = u'+'
return signstr
""" Convert the rads to a canonical form """
def canonical(self):
# Do nothing for the Angle class
return self
""" Return unicode string representation in DMS form """
def dms(self):
dgs = degrees(fabs(self.rads))
mns, dgs = modf(dgs)
scs, mns = modf(mns*60)
scs *= 60
return self.__get_sign_str() \
+ u'{0:03d}\u00B0'.format(int(dgs)) \
+ u'{0:02d}\u2032'.format(int(mns)) \
+ u'{0:05.2f}\u2033'.format(scs)
#+ u'{0:05.2f}'.format(scs).replace(u'.',u'\u2033')
""" Return unicode string representation in HMS form """
def hms(self):
# 1hr equals 15 degs
hrs = degrees(fabs(self.rads))/15.0
mns, hrs = modf(hrs)
scs, mns = modf(mns*60)
scs *= 60
return self.__get_sign_str() \
+ u'{0:02d}h '.format(int(hrs)) \
+ u'{0:02d}m '.format(int(mns)) \
+ u'{0:06.3f}s'.format(scs)
def __unicode__(self):
return self.dms()
def __str__(self):
return unicode(self).encode(sys.stdout.encoding or DEFAULT_ENCODING,
'replace')
"""
Angle subclass Longitude always keeps the radians value between 0 and 2pi
Useful for longitudes, Right Ascensions etc.
"""
class Longitude(Angle):
def __add__(self, other):
assert isinstance(other, Longitude), 'Unsupported type for addition'
return Longitude(self.rads + other.rads)
def __sub__(self,other):
assert isinstance(other, Longitude), 'Unsupported type for subtraction'
return Longitude(self.rads - other.rads)
""" Remove multiples of 2 pi from rads to get a value between 0 and 2 pi"""
def canonical(self):
two_pis = 2*pi
self.rads = pfmod(self.rads,two_pis)
return self
"""
Angle subclass Latitude always keeps the radians value between -pi/2 and pi/2
Useful for latitudes, declinations, etc.
"""
class Latitude(Angle):
def __add__(self, other):
assert isinstance(other, Latitude), 'Unsupported type for addition'
return Latitude(self.rads + other.rads)
def __sub__(self,other):
assert isinstance(other, Latitude), 'Unsupported type for subtraction'
return Latitude(self.rads - other.rads)
""" Remap the rads to lie between -pi/2 and +pi/2 """
def canonical(self):
# First mod it to lie between 0 and 2*pi
two_pis = 2*pi
self.rads = pfmod(self.rads,two_pis)
# Now map each quadrant so that the final result lies in -pi/2, pi/2
# 0 - pi/2: no change
# pi/2 - pi: map to range pi/2, 0
# pi - 3pi/2: map to range 0, -pi/2
# 3pi/2 - 2pi: map to range -pi/2, 0
if 0 <= self.rads <= pi/2:
pass
elif self.rads <= pi:
self.rads = pi - self.rads
elif self.rads <= 3*pi/2:
self.rads = -(self.rads - pi)
else: # 3*pi/2 < self.rads < 2*pi
self.rads -= two_pis
# Check that we did every thing right
assert fabs(self.rads) <= pi/2, 'Latitude should not exceed pi/2 radians either side of 0'
return self
""" Override hms to raise exception """
def hms(self):
raise RuntimeError('hms not supported for Latitude')
if __name__ == "__main__":
myangle = Angle(-radians(pi))
print str(myangle)
print myangle
print myangle.rads
print str(Angle(pi))
print Angle(pi/2)
myangle = Angle(pi) + Angle(pi/2)
print myangle
myangle += Angle(pi/2)
print myangle
myangle = Angle(-2*pi-radians(30))
print myangle, myangle.dms(), myangle.canonical()
print myangle.hms()
print Angle(0)
print Angle(1)
print Angle(1.0)
print repr(Angle(pi))
longitude = Longitude(-radians(pi))
print longitude
try:
latitude = Latitude(-pi)
except AssertionError as e:
print 'Error:', e
latitude = Latitude(-pi/2)
print latitude
try:
latitude.hms()
except RuntimeError as e:
print 'Error:', e
print latitude + Latitude(-pi/6) # -pi/3 (-60deg)
try:
latitude + longitude
except AssertionError as e:
print 'Error:', e
try:
longitude + latitude
except AssertionError as e:
print 'Error:', e
print Angle(pi/4) + Latitude(-pi/3) # -15deg
print Angle(pi/3) + Longitude(-pi/3) # +360