orbit 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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