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,128 @@
1
+ module Orbit
2
+
3
+ class OrbitGlobals
4
+
5
+ PI = 3.141592653589793
6
+ TWO_PI = 2.0 * OrbitGlobals::PI
7
+ RADS_PER_DEGREE = OrbitGlobals::PI / 180.0
8
+ DEGREES_PER_RAD = 180.0 / OrbitGlobals::PI
9
+
10
+ GM = 398601.2 # Earth gravitational constant, km^3/sec^2
11
+ GEO_SYNC_ALT = 42241.892 # km
12
+ EARTHDIAM = 12800.0 # km
13
+ DAYSIDEREAL = (23 * 3600) + (56 * 60) + 4.09 # sec
14
+ DAYSOLAR = (24 * 3600.0) # sec
15
+
16
+ AE = 1.0
17
+ AU = 149597870.0 # Astronomical unit (km) (IAU 76)
18
+ SR = 696000.0 # Solar radius (km) (IAU 76)
19
+ XKMPER = 6378.135 # Earth equatorial radius - kilometers (WGS '72)
20
+ F = 1.0 / 298.26 # Earth flattening (WGS '72)
21
+ GE = 398600.8 # Earth gravitational constant (WGS '72)
22
+ J2 = 1.0826158E-3 # J2 harmonic (WGS '72)
23
+ J3 = -2.53881E-6 # J3 harmonic (WGS '72)
24
+ J4 = -1.65597E-6 # J4 harmonic (WGS '72)
25
+ CK2 = OrbitGlobals::J2 / 2.0
26
+ CK4 = -3.0 * OrbitGlobals::J4 / 8.0
27
+ XJ3 = OrbitGlobals::J3
28
+ QO = OrbitGlobals::AE + 120.0 / OrbitGlobals::XKMPER
29
+ S = OrbitGlobals::AE + 78.0 / OrbitGlobals::XKMPER
30
+ MIN_PER_DAY = 1440.0 # Minutes per day (solar)
31
+ SEC_PER_DAY = 86400.0 # Seconds per day (solar)
32
+ OMEGAE = 1.00273790934 # Earth rotation per sidereal day
33
+ XKE = Math.sqrt(3600.0 * OrbitGlobals::GE /
34
+ (OrbitGlobals::XKMPER * OrbitGlobals::XKMPER * OrbitGlobals::XKMPER)) # sqrt(ge) ER^3/min^2
35
+ QOMS2T = ((OrbitGlobals::QO - OrbitGlobals::S) ** 4.0) #(QO - S)^4 ER^4
36
+
37
+
38
+ def self.sqr( n )
39
+ n ** 2
40
+ end
41
+
42
+ def self.deg_to_rad( deg )
43
+ deg * RADS_PER_DEGREE;
44
+ end
45
+
46
+ def self.rad_to_deg( rad )
47
+ rad * DEGREES_PER_RAD;
48
+ end
49
+
50
+
51
+ def self.fmod2p(arg)
52
+ modu = (arg % TWO_PI);
53
+
54
+ if (modu < 0.0)
55
+ modu += TWO_PI
56
+ end
57
+
58
+ return modu
59
+ end
60
+
61
+ # // ///////////////////////////////////////////////////////////////////////////
62
+ # // Globals.AcTan()
63
+ # // ArcTangent of sin(x) / cos(x). The advantage of this function over arctan()
64
+ # // is that it returns the correct quadrant of the angle.
65
+ def self.actan( sinx, cosx)
66
+ ret = nil
67
+
68
+ if (cosx == 0.0)
69
+ if (sinx > 0.0)
70
+ ret = PI / 2.0
71
+ else
72
+ ret = 3.0 * PI / 2.0
73
+ end
74
+ else
75
+ if (cosx > 0.0)
76
+ ret = Math.atan(sinx / cosx)
77
+ else
78
+ ret = PI + Math.atan(sinx / cosx)
79
+ end
80
+ end
81
+
82
+ return ret
83
+ end
84
+
85
+ def self.time_to_gmst(t)
86
+ jd = t.to_date.jd - 0.5
87
+ seconds = (t.hour * 3600) + (t.min * 60) + (t.sec).to_f + (t.subsec).to_f
88
+ fraction_of_day = seconds / 86400.0
89
+
90
+ jd += fraction_of_day
91
+
92
+ #puts "jd: #{jd}"
93
+
94
+ ut = (jd + 0.5 ) % 1.0;
95
+ jd = jd - ut
96
+ tu = (jd - 2451545.0) / 36525.0
97
+ gmst = 24110.54841 + tu * (8640184.812866 + tu * (0.093104 - tu * 6.2E-6));
98
+ gmst = ( gmst + 86400.0 * 1.00273790934 * ut ) % 86400.0
99
+ if (gmst < 0.0)
100
+ gmst += 86400.0 # "wrap" negative modulo value
101
+ end
102
+
103
+ gmst = (OrbitGlobals::TWO_PI * (gmst / 86400.0))
104
+
105
+ # puts "gmst: #{gmst}"
106
+
107
+ gmst
108
+ end
109
+
110
+ # /////////////////////////////////////////////////////////////////////
111
+ # ToLmst()
112
+ # Calculate Local Mean Sidereal Time for given longitude (for this date).
113
+ # The longitude is assumed to be in radians measured west from Greenwich.
114
+ # The return value is the angle, in radians, measuring eastward from the
115
+ # Vernal Equinox to the given longitude.
116
+ def self.time_to_lmst (t, lon)
117
+ gmst = OrbitGlobals.time_to_gmst( t )
118
+ lmst = ( gmst + lon ) % TWO_PI
119
+
120
+ # puts "long: #{lon}"
121
+ # puts "gmst: #{gmst}"
122
+ # puts "lmst: #{lmst}"
123
+
124
+ lmst
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,40 @@
1
+ module Orbit
2
+ class Satellite
3
+
4
+ attr_accessor :tle
5
+ attr_accessor :orbit
6
+
7
+ def initialize( tle_string )
8
+ @tle = Tle.new(tle_string)
9
+ @orbit = Orbit.new(@tle)
10
+ end
11
+
12
+ def eci_position_at_time( time )
13
+ since_epoch = ( time.utc.to_f - @tle.epoch.to_f )
14
+
15
+ eci_position_at_seconds_since_epoch( since_epoch )
16
+ end
17
+
18
+ def eci_position_at_seconds_since_epoch( time_since_epoch )
19
+ t = time_since_epoch / 60.0
20
+
21
+ p = @orbit.get_position( t ) #For whatever reason this is decimal minutes
22
+ end
23
+
24
+ def position_at_time( time )
25
+ seconds_since_epoch = ( time.utc.to_f - @tle.epoch.to_f )
26
+ position_at_seconds_since_epoch( seconds_since_epoch )
27
+ end
28
+
29
+ def position_at_seconds_since_epoch( time_since_epoch )
30
+ eci_position_at_seconds_since_epoch( time_since_epoch ).to_geo
31
+ end
32
+
33
+ def current_position
34
+ since_epoch = ( Time.new.utc.to_f - @tle.epoch.to_f )
35
+
36
+ position_at_seconds_since_epoch( since_epoch )
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,115 @@
1
+ module Orbit
2
+
3
+ class Site
4
+
5
+ attr_accessor :geo
6
+
7
+ def initialize( lat, lon, alt )
8
+ alt = alt / 1000 #km
9
+ @geo = GeocentricCoordinates.new( OrbitGlobals.deg_to_rad( lat ), OrbitGlobals.deg_to_rad( lon ), alt )
10
+ end
11
+
12
+ def latitude_rad
13
+ @geo.latitude_rad
14
+ end
15
+
16
+ def longitude_rad
17
+ @geo.longitude_rad
18
+ end
19
+
20
+ def latitude_deg
21
+ OrbitGlobals::rad_to_deg( latitude_rad )
22
+ end
23
+
24
+ def longitude_deg
25
+ OrbitGlobals::rad_to_deg( longitude_rad )
26
+ end
27
+
28
+ def altitude
29
+ @geo.altitude
30
+ end
31
+
32
+ def get_position(date)
33
+ return Eci.new(@geo, date)
34
+ end
35
+
36
+ def view_angle_to_satellite_at_time( sat, time )
37
+ sat_position = sat.eci_position_at_time( time )
38
+ topoLook = get_look_angle( sat_position )
39
+ end
40
+
41
+ def get_look_angle(eci)
42
+ # Calculate the ECI coordinates for this Site object at the time
43
+ # of interest.
44
+ date = eci.date
45
+ eciSite = Eci.new(@geo, date)
46
+ vecRgRate = Vector.new(eci.velocity.m_x - eciSite.velocity.m_x,
47
+ eci.velocity.m_y - eciSite.velocity.m_y,
48
+ eci.velocity.m_z - eciSite.velocity.m_z)
49
+
50
+ x = eci.position.m_x - eciSite.position.m_x
51
+ y = eci.position.m_y - eciSite.position.m_y
52
+ z = eci.position.m_z - eciSite.position.m_z
53
+ w = Math.sqrt(OrbitGlobals::sqr(x) + OrbitGlobals::sqr(y) + OrbitGlobals::sqr(z))
54
+
55
+ vecRange = Vector.new(x, y, z, w)
56
+
57
+ # The site's Local Mean Sidereal Time at the time of interest.
58
+ theta = OrbitGlobals.time_to_lmst( date, longitude_rad)
59
+
60
+ sin_lat = Math.sin(latitude_rad)
61
+ cos_lat = Math.cos(latitude_rad)
62
+ sin_theta = Math.sin(theta)
63
+ cos_theta = Math.cos(theta)
64
+
65
+ top_s = sin_lat * cos_theta * vecRange.m_x +
66
+ sin_lat * sin_theta * vecRange.m_y -
67
+ cos_lat * vecRange.m_z
68
+ top_e = -sin_theta * vecRange.m_x +
69
+ cos_theta * vecRange.m_y
70
+ top_z = cos_lat * cos_theta * vecRange.m_x +
71
+ cos_lat * sin_theta * vecRange.m_y +
72
+ sin_lat * vecRange.m_z
73
+ az = Math.atan(-top_e / top_s)
74
+
75
+ if (top_s > 0.0)
76
+ az += OrbitGlobals::PI
77
+ end
78
+
79
+ if (az < 0.0)
80
+ az += 2.0 * OrbitGlobals::PI
81
+ end
82
+
83
+ el = Math.asin(top_z / vecRange.m_w)
84
+ rate = (vecRange.m_x * vecRgRate.m_x +
85
+ vecRange.m_y * vecRgRate.m_y +
86
+ vecRange.m_z * vecRgRate.m_z) / vecRange.m_w
87
+
88
+ topo = TopocentricHorizonCoordinates.new(az, # azimuth, radians
89
+ el, # elevation, radians
90
+ vecRange.m_w, # range, km
91
+ rate) # rate, km / sec
92
+ #if WANT_ATMOSPHERIC_CORRECTION
93
+ # Elevation correction for atmospheric refraction.
94
+ # Reference: Astronomical Algorithms by Jean Meeus, pp. 101-104
95
+ # Note: Correction is meaningless when apparent elevation is below horizon
96
+ topo.elevation += OrbitGlobals::deg_to_rad((1.02 /
97
+ Math.tan(OrbitGlobals::deg_to_rad(OrbitGlobals::rad_to_deg(el) + 10.3 /
98
+ (OrbitGlobals::rad_to_deg(el) + 5.11)))) / 60.0)
99
+ if (topo.elevation < 0.0)
100
+ topo.elevation = el # Reset to true elevation
101
+ end
102
+
103
+ if (topo.elevation > (OrbitGlobals::PI / 2))
104
+ topo.elevation = (OrbitGlobals::PI / 2)
105
+ end
106
+ #endif
107
+ return topo
108
+
109
+ end
110
+
111
+
112
+
113
+
114
+ end
115
+ end
@@ -0,0 +1,127 @@
1
+ module Orbit
2
+
3
+ class Tle
4
+
5
+ attr_accessor :tle_string
6
+ attr_accessor :norad_num
7
+ attr_accessor :intl_desc
8
+ attr_accessor :set_number # TLE set number
9
+ attr_accessor :epoch_year # Epoch: Last two digits of year
10
+ attr_accessor :epoch_day # Epoch: Fractional Julian Day of year
11
+ attr_accessor :orbit_at_epoch # Orbit at epoch
12
+ attr_accessor :inclination # Inclination
13
+ attr_accessor :raan # R.A. ascending node
14
+ attr_accessor :eccentricity # Eccentricity
15
+ attr_accessor :arg_perigee # Argument of perigee
16
+ attr_accessor :mean_anomaly # Mean anomaly
17
+ attr_accessor :mean_motion # Mean motion
18
+ attr_accessor :mean_motion_dt # First time derivative of mean motion
19
+ attr_accessor :mean_motion_dt2 # Second time derivative of mean motion
20
+ attr_accessor :bstar_drag # BSTAR Drag
21
+
22
+
23
+ # ISS (ZARYA)
24
+ # 1 25544U 98067A 08264.51782528 −.00002182 00000-0 -11606-4 0 2927
25
+ # 2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537
26
+
27
+ FIELD_COLUMNS = {
28
+ norad_num: [1, 3,7],
29
+ classification: [1, 8,8],
30
+ launch_year: [1, 10,11],
31
+ launch_num: [1, 12,14],
32
+ launch_piece: [1, 15,17],
33
+ epoch_year: [1, 19,20],
34
+ epoch_day: [1, 21,32],
35
+ mean_motion_dt: [1, 34,43],
36
+ mean_motion_dt2: [1, 45,52],
37
+ bstar_drag: [1, 54,61],
38
+ number_zero: [1, 63,63],
39
+ element_num: [1, 65,68],
40
+ checksum_1: [1, 69,69],
41
+ inclination: [2, 9,16],
42
+ raan: [2, 18,25],
43
+ eccentricity: [2, 27,33],
44
+ arg_perigee: [2, 35,42],
45
+ mean_anomaly: [2, 44,51],
46
+ mean_motion: [2, 53,63],
47
+ revolution_num: [2, 64,68],
48
+ checksum_2: [2, 69,69]
49
+ }
50
+
51
+
52
+ def initialize( s )
53
+ @tle_string = s
54
+
55
+ # puts @tle_string
56
+
57
+ @norad_num = get_field( :norad_num )
58
+ @classification = get_field( :classification )
59
+ @launch_year = get_field( :launch_year )
60
+ @launch_num = get_field( :launch_num )
61
+ @launch_piece = get_field( :launch_piece )
62
+ @mean_motion_dt = get_field( :mean_motion_dt ).to_f
63
+ @mean_motion_dt2 = exp_to_float( "0." + get_field( :mean_motion_dt2 ) ).to_f
64
+ @bstar_drag = exp_to_float( "0." + get_field( :bstar_drag ) ).to_f
65
+ @element_num = get_field( :element_num ).to_f
66
+ @inclination = get_field( :inclination ).to_f
67
+ @raan = get_field( :raan ).to_f
68
+ @eccentricity = exp_to_float( "0." + get_field( :eccentricity ) ).to_f
69
+ @arg_perigee = get_field( :arg_perigee ).to_f
70
+ @mean_anomaly = get_field( :mean_anomaly ).to_f
71
+ @mean_motion = get_field( :mean_motion ).to_f
72
+ @revolution_num = get_field( :revolution_num ).to_f
73
+ end
74
+
75
+ def exp_to_float( exp )
76
+ # puts "exp: #{exp}"
77
+ parts = exp.split( "-" )
78
+ exp_part = -0.1
79
+
80
+ if parts.count < 2
81
+ parts = exp.split( " " )
82
+ exp_part = 0.1
83
+ end
84
+
85
+ float = parts[0].to_f * ( exp_part ** parts[1].to_f )
86
+ end
87
+
88
+ def get_field( field )
89
+ lines = @tle_string.split( "\n" )
90
+
91
+ line_num = FIELD_COLUMNS[field][0]
92
+ substring_start = FIELD_COLUMNS[field][1] - 1 # Convert to zero-base
93
+ substring_end = FIELD_COLUMNS[field][2] - 1 # Convert to zero-base
94
+
95
+ lines[line_num][substring_start..substring_end].strip
96
+ end
97
+
98
+ def to_s
99
+ hash = {}
100
+ instance_variables.each {|var| hash[var.to_s.delete("@")] = instance_variable_get(var) }
101
+
102
+ hash.to_s
103
+ end
104
+
105
+ def epoch
106
+ epoch_year = get_field( :epoch_year ).to_f
107
+ epoch_day = get_field( :epoch_day ).to_f
108
+
109
+ epoch_year = epoch_year < 57 ? ( epoch_year + 2000 ) : ( epoch_year + 1900 )
110
+
111
+ epoch = Time.at( Time.utc( epoch_year ).to_i + ( ( epoch_day - 1 ) * OrbitGlobals::SEC_PER_DAY ) )
112
+
113
+ epoch = epoch.utc
114
+
115
+ # puts "epoch_year: #{epoch_year}"
116
+ # puts "epoch_day: #{epoch_day}"
117
+ # puts "epoch: #{epoch}"
118
+
119
+ epoch
120
+ end
121
+
122
+ # def epoch_julian
123
+ # epoch.
124
+ # end
125
+
126
+ end
127
+ end
@@ -0,0 +1,33 @@
1
+ module Orbit
2
+
3
+ class TopocentricHorizonCoordinates
4
+ #/ <summary>
5
+ #/ The azimuth, in radians.
6
+ #/ </summary>
7
+ attr_accessor :azimuth
8
+
9
+ #/ <summary>
10
+ #/ The elevation, in radians.
11
+ #/ </summary>
12
+ attr_accessor :elevation
13
+
14
+ #/ <summary>
15
+ #/ The range, in kilometers.
16
+ #/ </summary>
17
+ attr_accessor :range
18
+
19
+ #/ <summary>
20
+ #/ The range rate, in kilometers per second.
21
+ #/ A negative value means "towards observer".
22
+ #/ </summary>
23
+ attr_accessor :range_rate
24
+
25
+ def initialize( az, el, r, rr )
26
+ @azimuth = az
27
+ @elevation = el
28
+ @range = r
29
+ @range_rate = rr
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,56 @@
1
+ module Orbit
2
+
3
+ class Vector
4
+ attr_accessor :m_x
5
+ attr_accessor :m_y
6
+ attr_accessor :m_z
7
+ attr_accessor :m_w
8
+
9
+ def initialize( x = nil, y = nil, z = nil, w = nil )
10
+ @m_x = x
11
+ @m_y = y
12
+ @m_z = z
13
+ @m_w = w
14
+ end
15
+
16
+ # ##################################/
17
+ # Multiply each component in the vector by 'factor'.
18
+ def mul(factor)
19
+ #puts "m_x: #{@m_x}, factor: #{factor}"
20
+
21
+ @m_x = @m_x * factor
22
+ @m_y *= factor
23
+ @m_z *= factor
24
+ @m_w *= (factor).abs if @m_w
25
+ end
26
+
27
+ # ##################################/
28
+ # Subtract a vector from this one.
29
+ def sub(vec)
30
+ @m_x -= vec.m_x
31
+ @m_y -= vec.m_y
32
+ @m_z -= vec.m_z
33
+ @m_w -= vec.m_w
34
+ end
35
+
36
+ # ##################################/
37
+ # Calculate the angle between this vector and another
38
+ def angle(vec)
39
+ return Math.acos(dot(vec) / (magnitude() * vec.magnitude()))
40
+ end
41
+
42
+ # ##################################/
43
+ def magnitude
44
+ return Math.sqrt((@m_x * @m_x) + (@m_y * @m_y) + (@m_z * @m_z))
45
+ end
46
+
47
+
48
+ # ##################################/
49
+ # Return the dot product
50
+ def dot(vec)
51
+ return (@m_x * vec.m_x) + (@m_y * vec.m_y) + (@m_z * vec.m_z)
52
+ end
53
+
54
+
55
+ end
56
+ end