orbit 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,261 @@
1
+ module Orbit
2
+ class NoradBase
3
+
4
+ attr_accessor :m_satInc # inclination
5
+ attr_accessor :m_satEcc # eccentricity
6
+
7
+ attr_accessor :m_cosio
8
+ attr_accessor :m_betao2
9
+ attr_accessor :m_s4
10
+ attr_accessor :m_eta
11
+ attr_accessor :m_coef1
12
+ attr_accessor :m_sinio
13
+ attr_accessor :m_xnodot
14
+ attr_accessor :m_aycof
15
+
16
+ attr_accessor :m_eosq
17
+ attr_accessor :m_xnodp
18
+ attr_accessor :m_tsi
19
+ attr_accessor :m_coef
20
+ attr_accessor :m_c4
21
+ attr_accessor :m_omgdot
22
+ attr_accessor :m_xlcof
23
+ attr_accessor :m_x3thm1
24
+ attr_accessor :m_aodp
25
+ attr_accessor :m_perigee
26
+ attr_accessor :m_eeta
27
+ attr_accessor :m_c3
28
+ attr_accessor :m_xmdot
29
+ attr_accessor :m_t2cof
30
+ attr_accessor :m_theta2
31
+ attr_accessor :m_betao
32
+ attr_accessor :m_qoms24
33
+ attr_accessor :m_etasq
34
+ attr_accessor :m_c1
35
+ attr_accessor :m_x1mth2
36
+ attr_accessor :m_xnodcf
37
+ attr_accessor :m_x7thm1
38
+
39
+ def initialize( nothing )
40
+ # Initialize any variables which are time-independent when
41
+ # calculating the ECI coordinates of the satellite.
42
+ @m_satInc = @orbit.inclination
43
+ @m_satEcc = @orbit.eccentricity
44
+
45
+ @m_cosio = Math.cos(@m_satInc)
46
+ @m_theta2 = @m_cosio * @m_cosio
47
+ @m_x3thm1 = 3.0 * @m_theta2 - 1.0
48
+ @m_eosq = @m_satEcc * @m_satEcc
49
+ @m_betao2 = 1.0 - @m_eosq
50
+ @m_betao = Math.sqrt(@m_betao2)
51
+
52
+ # The "recovered" semimajor axis and mean motion.
53
+ @m_aodp = @orbit.semi_major
54
+ @m_xnodp = @orbit.mean_motion
55
+
56
+ # For perigee below 156 km, the values of OrbitGlobals::S and OrbitGlobals::QOMS2T are altered.
57
+ @m_perigee = OrbitGlobals::XKMPER * (@m_aodp * (1.0 - @m_satEcc) - OrbitGlobals::AE)
58
+
59
+ @m_s4 = OrbitGlobals::S
60
+ @m_qoms24 = OrbitGlobals::QOMS2T
61
+
62
+ if (@m_perigee < 156.0)
63
+ @m_s4 = @m_perigee - 78.0
64
+
65
+ if (@m_perigee <= 98.0)
66
+ @m_s4 = 20.0
67
+ end
68
+
69
+ @m_qoms24 = (((120.0 - @m_s4) * OrbitGlobals::AE / OrbitGlobals::XKMPER ) ** 4.0)
70
+ @m_s4 = @m_s4 / OrbitGlobals::XKMPER + OrbitGlobals::AE
71
+ end
72
+
73
+ pinvsq = 1.0 / (@m_aodp * @m_aodp * @m_betao2 * @m_betao2)
74
+
75
+ @m_tsi = 1.0 / (@m_aodp - @m_s4)
76
+ @m_eta = @m_aodp * @m_satEcc * @m_tsi
77
+ @m_etasq = @m_eta * @m_eta
78
+ @m_eeta = @m_satEcc * @m_eta
79
+
80
+ psisq = (1.0 - @m_etasq).abs
81
+
82
+ @m_coef = @m_qoms24 * (@m_tsi ** 4.0)
83
+ @m_coef1 = @m_coef / (psisq ** 3.5)
84
+
85
+ c2 = @m_coef1 * @m_xnodp *
86
+ (@m_aodp * (1.0 + 1.5 * @m_etasq + @m_eeta * (4.0 + @m_etasq)) +
87
+ 0.75 * OrbitGlobals::CK2 * @m_tsi / psisq * @m_x3thm1 *
88
+ (8.0 + 3.0 * @m_etasq * (8.0 + @m_etasq)))
89
+
90
+ @m_c1 = @orbit.bstar * c2
91
+ @m_sinio = Math.sin(@m_satInc)
92
+
93
+ a3ovk2 = -OrbitGlobals::XJ3 / OrbitGlobals::CK2 * (OrbitGlobals::AE ** 3.0)
94
+
95
+ @m_c3 = @m_coef * @m_tsi * a3ovk2 * @m_xnodp * OrbitGlobals::AE * @m_sinio / @m_satEcc
96
+ @m_x1mth2 = 1.0 - @m_theta2
97
+ @m_c4 = 2.0 * @m_xnodp * @m_coef1 * @m_aodp * @m_betao2 *
98
+ (@m_eta * (2.0 + 0.5 * @m_etasq) +
99
+ @m_satEcc * (0.5 + 2.0 * @m_etasq) -
100
+ 2.0 * OrbitGlobals::CK2 * @m_tsi / (@m_aodp * psisq) *
101
+ (-3.0 * @m_x3thm1 * (1.0 - 2.0 * @m_eeta + @m_etasq * (1.5 - 0.5 * @m_eeta)) +
102
+ 0.75 * @m_x1mth2 *
103
+ (2.0 * @m_etasq - @m_eeta * (1.0 + @m_etasq)) *
104
+ Math.cos(2.0 * @orbit.arg_perigee)))
105
+
106
+ theta4 = @m_theta2 * @m_theta2
107
+ temp1 = 3.0 * OrbitGlobals::CK2 * pinvsq * @m_xnodp
108
+ temp2 = temp1 * OrbitGlobals::CK2 * pinvsq
109
+ temp3 = 1.25 * OrbitGlobals::CK4 * pinvsq * pinvsq * @m_xnodp
110
+
111
+ @m_xmdot = @m_xnodp + 0.5 * temp1 * @m_betao * @m_x3thm1 +
112
+ 0.0625 * temp2 * @m_betao *
113
+ (13.0 - 78.0 * @m_theta2 + 137.0 * theta4)
114
+
115
+ x1m5th = 1.0 - 5.0 * @m_theta2
116
+
117
+ @m_omgdot = -0.5 * temp1 * x1m5th + 0.0625 * temp2 *
118
+ (7.0 - 114.0 * @m_theta2 + 395.0 * theta4) +
119
+ temp3 * (3.0 - 36.0 * @m_theta2 + 49.0 * theta4)
120
+
121
+ xhdot1 = -temp1 * @m_cosio
122
+
123
+ @m_xnodot = xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * @m_theta2) +
124
+ 2.0 * temp3 * (3.0 - 7.0 * @m_theta2)) * @m_cosio
125
+ @m_xnodcf = 3.5 * @m_betao2 * xhdot1 * @m_c1
126
+ @m_t2cof = 1.5 * @m_c1
127
+ @m_xlcof = 0.125 * a3ovk2 * @m_sinio *
128
+ (3.0 + 5.0 * @m_cosio) / (1.0 + @m_cosio)
129
+ @m_aycof = 0.25 * a3ovk2 * @m_sinio
130
+ @m_x7thm1 = 7.0 * @m_theta2 - 1.0
131
+ end
132
+
133
+ def final_position( incl, omega, e,
134
+ a, xl, xnode,
135
+ xn, tsince)
136
+ if (e * e) > 1.0
137
+ Exception.new( "Error in satellite data" )
138
+ #throw new PropagationException("Error in satellite data")
139
+ end
140
+
141
+ beta = Math.sqrt(1.0 - e * e)
142
+
143
+ # Long period periodics
144
+ axn = e * Math.cos(omega)
145
+ temp = 1.0 / (a * beta * beta)
146
+ xll = temp * @m_xlcof * axn
147
+ aynl = temp * @m_aycof
148
+ xlt = xl + xll
149
+ ayn = e * Math.sin(omega) + aynl
150
+
151
+ # Solve Kepler's Equation
152
+ capu = OrbitGlobals::fmod2p(xlt - xnode)
153
+ temp2 = capu
154
+ temp3 = 0.0
155
+ temp4 = 0.0
156
+ temp5 = 0.0
157
+ temp6 = 0.0
158
+ sinepw = 0.0
159
+ cosepw = 0.0
160
+
161
+ fDone = false
162
+ i = 1
163
+ while i <= 10 && !fDone do
164
+ #for (int i = 1 (i <= 10) && !fDone i++)
165
+ sinepw = Math.sin(temp2)
166
+ cosepw = Math.cos(temp2)
167
+ temp3 = axn * sinepw
168
+ temp4 = ayn * cosepw
169
+ temp5 = axn * cosepw
170
+ temp6 = ayn * sinepw
171
+
172
+ epw = (capu - temp4 + temp3 - temp2) /
173
+ (1.0 - temp5 - temp6) + temp2
174
+
175
+ if ((epw - temp2).abs <= 1.0e-06)
176
+ fDone = true
177
+ else
178
+ temp2 = epw
179
+ end
180
+
181
+ i += 1
182
+ end
183
+
184
+ # Short period preliminary quantities
185
+ ecose = temp5 + temp6
186
+ esine = temp3 - temp4
187
+ elsq = axn * axn + ayn * ayn
188
+ temp = 1.0 - elsq
189
+ pl = a * temp
190
+ r = a * (1.0 - ecose)
191
+ temp1 = 1.0 / r
192
+ rdot = OrbitGlobals::XKE * Math.sqrt(a) * esine * temp1
193
+ rfdot = OrbitGlobals::XKE * Math.sqrt(pl) * temp1
194
+ temp2 = a * temp1
195
+ betal = Math.sqrt(temp)
196
+ temp3 = 1.0 / (1.0 + betal)
197
+ cosu = temp2 * (cosepw - axn + ayn * esine * temp3)
198
+ sinu = temp2 * (sinepw - ayn - axn * esine * temp3)
199
+ u = OrbitGlobals.actan(sinu, cosu)
200
+ sin2u = 2.0 * sinu * cosu
201
+ cos2u = 2.0 * cosu * cosu - 1.0
202
+
203
+ temp = 1.0 / pl
204
+ temp1 = OrbitGlobals::CK2 * temp
205
+ temp2 = temp1 * temp
206
+
207
+ # Update for short periodics
208
+ rk = r * (1.0 - 1.5 * temp2 * betal * @m_x3thm1) +
209
+ 0.5 * temp1 * @m_x1mth2 * cos2u
210
+ uk = u - 0.25 * temp2 * @m_x7thm1 * sin2u
211
+ xnodek = xnode + 1.5 * temp2 * @m_cosio * sin2u
212
+ xinck = incl + 1.5 * temp2 * @m_cosio * @m_sinio * cos2u
213
+ rdotk = rdot - xn * temp1 * @m_x1mth2 * sin2u
214
+ rfdotk = rfdot + xn * temp1 * (@m_x1mth2 * cos2u + 1.5 * @m_x3thm1)
215
+
216
+ # Orientation vectors
217
+ sinuk = Math.sin(uk)
218
+ cosuk = Math.cos(uk)
219
+ sinik = Math.sin(xinck)
220
+ cosik = Math.cos(xinck)
221
+ sinnok = Math.sin(xnodek)
222
+ cosnok = Math.cos(xnodek)
223
+ xmx = -sinnok * cosik
224
+ xmy = cosnok * cosik
225
+ ux = xmx * sinuk + cosnok * cosuk
226
+ uy = xmy * sinuk + sinnok * cosuk
227
+ uz = sinik * sinuk
228
+ vx = xmx * cosuk - cosnok * sinuk
229
+ vy = xmy * cosuk - sinnok * sinuk
230
+ vz = sinik * cosuk
231
+
232
+ # Position
233
+ x = rk * ux
234
+ y = rk * uy
235
+ z = rk * uz
236
+
237
+ vecPos = Vector.new(x, y, z)
238
+
239
+ # puts "@orbit.epoch_time : #{@orbit.epoch_time}"
240
+
241
+ gmt = @orbit.epoch_time + ( tsince * 60.0 )
242
+
243
+ # Validate on altitude
244
+ altKm = (vecPos.magnitude * (OrbitGlobals::XKMPER / OrbitGlobals::AE))
245
+
246
+ if (altKm < OrbitGlobals::XKMPER)
247
+ Exception.new( "Decay Exception" )
248
+ #throw new DecayException(gmt, @orbit.SatNameLong)
249
+ end
250
+
251
+ # Velocity
252
+ xdot = rdotk * ux + rfdotk * vx
253
+ ydot = rdotk * uy + rfdotk * vy
254
+ zdot = rdotk * uz + rfdotk * vz
255
+
256
+ vecVel = Vector.new(xdot, ydot, zdot)
257
+
258
+ return Eci.new_with_pos_vel_gmt(vecPos, vecVel, gmt)
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,4 @@
1
+ module Orbit
2
+ class NoradSDP4
3
+ end
4
+ end
@@ -0,0 +1,101 @@
1
+ module Orbit
2
+ class NoradSGP4 < NoradBase
3
+
4
+ attr_accessor :orbit
5
+
6
+ attr_accessor :m_c5
7
+ attr_accessor :m_omgcof
8
+ attr_accessor :m_xmcof
9
+ attr_accessor :m_delmo
10
+ attr_accessor :m_sinmo
11
+
12
+
13
+ def initialize( orbit )
14
+ @orbit = orbit
15
+
16
+ super
17
+
18
+ @m_c5 = 2.0 * @m_coef1 * @m_aodp * @m_betao2 *
19
+ (1.0 + 2.75 * (@m_etasq + @m_eeta) + @m_eeta * @m_etasq)
20
+ @m_omgcof = orbit.bstar * @m_c3 * Math.cos(orbit.arg_perigee)
21
+ @m_xmcof = -(2.0 / 3.0) * @m_coef * orbit.bstar * OrbitGlobals::AE / @m_eeta
22
+ @m_delmo = ((1.0 + @m_eta * Math.cos(orbit.mean_anomaly) ) ** 3.0)
23
+ @m_sinmo = Math.sin(orbit.mean_anomaly)
24
+
25
+ end
26
+
27
+ def get_position(tsince)
28
+ # puts "get_position( #{tsince} )"
29
+
30
+ # For @m_perigee less than 220 kilometers, the isimp flag is set and
31
+ # the equations are truncated to linear variation in Math.Sqrt a and
32
+ # quadratic variation in mean anomaly. Also, the @m_c3 term, the
33
+ # delta omega term, and the delta m term are dropped.
34
+ isimp = false
35
+ if ((@m_aodp * (1.0 - @m_satEcc) / OrbitGlobals::AE) < (220.0 / OrbitGlobals::XKMPER + OrbitGlobals::AE))
36
+ isimp = true
37
+ end
38
+
39
+ d2 = 0.0
40
+ d3 = 0.0
41
+ d4 = 0.0
42
+
43
+ t3cof = 0.0
44
+ t4cof = 0.0
45
+ t5cof = 0.0
46
+
47
+ if (!isimp)
48
+ c1sq = @m_c1 * @m_c1
49
+
50
+ d2 = 4.0 * @m_aodp * @m_tsi * c1sq
51
+
52
+ temp = d2 * @m_tsi * @m_c1 / 3.0
53
+
54
+ d3 = (17.0 * @m_aodp + @m_s4) * temp
55
+ d4 = 0.5 * temp * @m_aodp * @m_tsi *
56
+ (221.0 * @m_aodp + 31.0 * @m_s4) * @m_c1
57
+ t3cof = d2 + 2.0 * c1sq
58
+ t4cof = 0.25 * (3.0 * d3 + @m_c1 * (12.0 * d2 + 10.0 * c1sq))
59
+ t5cof = 0.2 * (3.0 * d4 + 12.0 * @m_c1 * d3 + 6.0 *
60
+ d2 * d2 + 15.0 * c1sq * (2.0 * d2 + c1sq))
61
+ end
62
+
63
+ # Update for secular gravity and atmospheric drag.
64
+ xmdf = orbit.mean_anomaly + @m_xmdot * tsince
65
+ omgadf = orbit.arg_perigee + @m_omgdot * tsince
66
+ xnoddf = orbit.raan + @m_xnodot * tsince
67
+ omega = omgadf
68
+ xmp = xmdf
69
+ tsq = tsince * tsince
70
+ xnode = xnoddf + @m_xnodcf * tsq
71
+ tempa = 1.0 - @m_c1 * tsince
72
+ tempe = orbit.bstar * @m_c4 * tsince
73
+ templ = @m_t2cof * tsq
74
+
75
+ if (!isimp)
76
+ delomg = @m_omgcof * tsince
77
+ delm = @m_xmcof * (((1.0 + @m_eta * Math.cos(xmdf) ) ** 3.0) - @m_delmo)
78
+ temp = delomg + delm
79
+
80
+ xmp = xmdf + temp
81
+ omega = omgadf - temp
82
+
83
+ tcube = tsq * tsince
84
+ tfour = tsince * tcube
85
+
86
+ tempa = tempa - d2 * tsq - d3 * tcube - d4 * tfour
87
+ tempe = tempe + orbit.bstar * @m_c5 * (Math.sin(xmp) - @m_sinmo)
88
+ templ = templ + t3cof * tcube + tfour * (t4cof + tsince * t5cof)
89
+ end
90
+
91
+ a = @m_aodp * (tempa ** 2)
92
+ e = @m_satEcc - tempe
93
+
94
+
95
+ xl = xmp + omega + xnode + @m_xnodp * templ
96
+ xn = OrbitGlobals::XKE / (a ** 1.5)
97
+
98
+ return final_position(@m_satInc, omgadf, e, a, xl, xnode, xn, tsince)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,163 @@
1
+ module Orbit
2
+ class Orbit
3
+ attr_accessor :tle
4
+
5
+ # TLE caching variables
6
+ attr_accessor :m_Inclination
7
+ attr_accessor :m_Eccentricity
8
+ attr_accessor :m_RAAN
9
+ attr_accessor :m_ArgPerigee
10
+ attr_accessor :m_BStar
11
+ attr_accessor :m_Drag
12
+ attr_accessor :m_TleMeanMotion
13
+ attr_accessor :m_MeanAnomaly
14
+
15
+ # Caching variables recovered from the input TLE elements
16
+ attr_accessor :m_aeAxisSemiMajorRec # semimajor axis, in AE units
17
+ attr_accessor :m_aeAxisSemiMinorRec # semiminor axis, in AE units
18
+ attr_accessor :m_rmMeanMotionRec # radians per minute
19
+ attr_accessor :m_kmPerigeeRec # perigee, in km
20
+ attr_accessor :m_kmApogeeRec # apogee, in km
21
+
22
+ attr_accessor :epoch
23
+
24
+ attr_accessor :norad_model
25
+ attr_accessor :period
26
+
27
+ def initialize( tle )
28
+ @tle = tle
29
+
30
+
31
+ @epoch = tle.epoch
32
+ # puts "Orbit @epoch is now: #{@epoch}"
33
+
34
+
35
+ @m_Inclination = OrbitGlobals.deg_to_rad(@tle.inclination)
36
+ @m_Eccentricity = @tle.eccentricity
37
+ @m_RAAN = OrbitGlobals.deg_to_rad(@tle.raan)
38
+ @m_ArgPerigee = OrbitGlobals.deg_to_rad(@tle.arg_perigee)
39
+ @m_BStar = @tle.bstar_drag
40
+ @m_Drag = @tle.mean_motion_dt
41
+ @m_MeanAnomaly = OrbitGlobals.deg_to_rad(@tle.mean_anomaly)
42
+ @m_TleMeanMotion = @tle.mean_motion
43
+
44
+ # Recover the original mean motion and semimajor axis from the
45
+ # input elements.
46
+ mm = tle_mean_motion
47
+ rpmin = mm * OrbitGlobals::TWO_PI / OrbitGlobals::MIN_PER_DAY # rads per minute
48
+
49
+ a1 = (OrbitGlobals::XKE / rpmin) ** (2.0 / 3.0)
50
+ e = eccentricity
51
+ i = inclination
52
+ temp = (1.5 * OrbitGlobals::CK2 * (3.0 * (Math.cos(i) ** 2) - 1.0) /
53
+ ( (1.0 - e * e) ** 1.5 ) )
54
+ delta1 = temp / (a1 * a1)
55
+ a0 = a1 *
56
+ (1.0 - delta1 *
57
+ ((1.0 / 3.0) + delta1 *
58
+ (1.0 + 134.0 / 81.0 * delta1)))
59
+
60
+ delta0 = temp / (a0 * a0)
61
+
62
+ @m_rmMeanMotionRec = rpmin / (1.0 + delta0)
63
+ @m_aeAxisSemiMajorRec = a0 / (1.0 - delta0)
64
+ @m_aeAxisSemiMinorRec = @m_aeAxisSemiMajorRec * Math.sqrt(1.0 - (e * e))
65
+ @m_kmPerigeeRec = OrbitGlobals::XKMPER * (m_aeAxisSemiMajorRec * (1.0 - e) - OrbitGlobals::AE)
66
+ @m_kmApogeeRec = OrbitGlobals::XKMPER * (m_aeAxisSemiMajorRec * (1.0 + e) - OrbitGlobals::AE)
67
+
68
+
69
+
70
+ @period_minutes = calculate_period_minutes
71
+
72
+ if @period_minutes > 225
73
+ @norad_model = NoradSDP4.new( self )
74
+ else
75
+ @norad_model = NoradSGP4.new( self )
76
+ end
77
+ end
78
+
79
+ def calculate_period_minutes
80
+ if @tle.mean_motion == 0
81
+ minutes = 0.0
82
+ else
83
+ minutes = (OrbitGlobals::TWO_PI / @tle.mean_motion)
84
+ end
85
+
86
+ minutes
87
+ end
88
+
89
+ def get_position(minutesPastEpoch)
90
+ # puts "Orbit.get_position( #{minutesPastEpoch} )"
91
+
92
+ eci = @norad_model.get_position(minutesPastEpoch)
93
+
94
+ # puts "Position: #{eci.m_Position.m_x}"
95
+
96
+ eci.scale_pos_vector(OrbitGlobals::XKMPER / OrbitGlobals::AE) # km
97
+ eci.scale_vel_vector((OrbitGlobals::XKMPER / OrbitGlobals::AE) *
98
+ (OrbitGlobals::MIN_PER_DAY / 86400.0)) # km/sec
99
+ return eci
100
+ end
101
+
102
+ def epoch_time
103
+ @epoch
104
+ end
105
+
106
+ def semi_major
107
+ return @m_aeAxisSemiMajorRec
108
+ end
109
+ def semi_minor
110
+ return @m_aeAxisSemiMinorRec
111
+ end
112
+ def mean_motion
113
+ return @m_rmMeanMotionRec
114
+ end
115
+ def major
116
+ return 2.0 * SemiMajor
117
+ end
118
+ def minor
119
+ return 2.0 * SemiMinor
120
+ end
121
+ def perigee
122
+ return @m_kmPerigeeRec
123
+ end
124
+ def apogee
125
+ return @m_kmApogeeRec
126
+ end
127
+
128
+ def inclination
129
+ return @m_Inclination
130
+ end
131
+ def eccentricity
132
+ return @m_Eccentricity
133
+ end
134
+ def raan
135
+ return @m_RAAN
136
+ end
137
+ def arg_perigee
138
+ return @m_ArgPerigee
139
+ end
140
+ def bstar
141
+ return @m_BStar
142
+ end
143
+ def drag
144
+ return @m_Drag
145
+ end
146
+ def mean_anomaly
147
+ return @m_MeanAnomaly
148
+ end
149
+ def tle_mean_motion
150
+ return @m_TleMeanMotion
151
+ end
152
+
153
+ def sat_norad_id
154
+ return @tle.norad_number
155
+ end
156
+ def sat_name
157
+ return @tle.name
158
+ end
159
+ def sat_name_long
160
+ return SatName + " #" + SatNoradId
161
+ end
162
+ end
163
+ end