astronoby 0.0.1 → 0.2.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.
@@ -4,39 +4,175 @@ require "bigdecimal/math"
4
4
 
5
5
  module Astronoby
6
6
  class Angle
7
- UNITS = [
8
- DEGREES = :degrees,
9
- RADIANS = :radians
10
- ].freeze
7
+ include Comparable
11
8
 
12
- UNIT_CLASS_NAMES = {
13
- DEGREES => "Astronoby::Degree",
14
- RADIANS => "Astronoby::Radian"
15
- }
9
+ PRECISION = 14
10
+ PI = BigMath.PI(PRECISION)
11
+ PI_IN_DEGREES = BigDecimal("180")
16
12
 
17
- PI = BigMath.PI(10)
13
+ FULL_CIRCLE_IN_RADIANS = (2 * PI)
14
+
15
+ RADIAN_PER_HOUR = PI / BigDecimal("12")
16
+ MINUTES_PER_DEGREE = BigDecimal("60")
17
+ MINUTES_PER_HOUR = BigDecimal("60")
18
+ SECONDS_PER_MINUTE = BigDecimal("60")
19
+ SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE
20
+
21
+ FORMATS = %i[dms hms].freeze
18
22
 
19
23
  class << self
20
- UNIT_CLASS_NAMES.each do |unit, class_name|
21
- define_method("as_#{unit}") do |angle|
22
- Kernel.const_get(class_name).new(angle)
23
- end
24
+ def zero
25
+ new(0)
26
+ end
27
+
28
+ def as_radians(radians)
29
+ normalized_radians = radians.remainder(FULL_CIRCLE_IN_RADIANS)
30
+ new(normalized_radians)
31
+ end
32
+
33
+ def as_degrees(degrees)
34
+ radians = degrees / PI_IN_DEGREES * PI
35
+ as_radians(radians)
36
+ end
37
+
38
+ def as_hours(hours)
39
+ radians = hours * RADIAN_PER_HOUR
40
+ as_radians(radians)
41
+ end
42
+
43
+ def as_hms(hour, minute, second)
44
+ hours = hour + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR
45
+ as_hours(hours)
46
+ end
47
+
48
+ def as_dms(degree, minute, second)
49
+ sign = degree.negative? ? -1 : 1
50
+ degrees = degree.abs + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR
51
+ as_degrees(sign * degrees)
52
+ end
53
+
54
+ def asin(ratio)
55
+ radians = Math.asin(ratio)
56
+ as_radians(radians)
57
+ end
58
+
59
+ def acos(ratio)
60
+ radians = Math.acos(ratio)
61
+ as_radians(radians)
62
+ end
63
+
64
+ def atan(ratio)
65
+ radians = Math.atan(ratio)
66
+ as_radians(radians)
24
67
  end
25
68
  end
26
69
 
27
- UNITS.each do |unit|
28
- define_method("to_#{unit}") do
29
- raise NotImplementedError, "#{self.class} must implement #to_#{unit} method."
70
+ attr_reader :radians
71
+
72
+ def initialize(radians)
73
+ @radians = if radians.is_a?(Integer) || radians.is_a?(BigDecimal)
74
+ BigDecimal(radians)
75
+ else
76
+ BigDecimal(radians, PRECISION)
30
77
  end
78
+ freeze
31
79
  end
32
80
 
33
- def initialize(angle, unit:)
34
- @angle = BigDecimal(angle)
35
- @unit = unit
81
+ def degrees
82
+ @radians * PI_IN_DEGREES / PI
36
83
  end
37
84
 
38
- def value
39
- @angle
85
+ def hours
86
+ @radians / RADIAN_PER_HOUR
87
+ end
88
+
89
+ def +(other)
90
+ self.class.as_radians(radians + other.radians)
91
+ end
92
+
93
+ def -(other)
94
+ self.class.as_radians(@radians - other.radians)
95
+ end
96
+
97
+ def sin
98
+ Math.sin(radians)
99
+ end
100
+
101
+ def cos
102
+ Math.cos(radians)
103
+ end
104
+
105
+ def tan
106
+ Math.tan(radians)
107
+ end
108
+
109
+ def positive?
110
+ radians > 0
111
+ end
112
+
113
+ def negative?
114
+ radians < 0
115
+ end
116
+
117
+ def zero?
118
+ radians.zero?
119
+ end
120
+
121
+ def ==(other)
122
+ other.is_a?(self.class) && radians == other.radians
123
+ end
124
+ alias_method :eql?, :==
125
+
126
+ def hash
127
+ [radians, self.class].hash
128
+ end
129
+
130
+ def <=>(other)
131
+ return nil unless other.is_a?(self.class)
132
+
133
+ radians <=> other.radians
134
+ end
135
+
136
+ def str(format)
137
+ case format
138
+ when :dms then to_dms(degrees).format
139
+ when :hms then to_hms(hours).format
140
+ else
141
+ raise UnsupportedFormatError.new(
142
+ "Expected a format between #{FORMATS.join(", ")}, got #{format}"
143
+ )
144
+ end
145
+ end
146
+
147
+ def to_dms(deg)
148
+ sign = deg.negative? ? "-" : "+"
149
+ absolute_degrees = deg.abs
150
+ degrees = absolute_degrees.floor
151
+ decimal_minutes = MINUTES_PER_DEGREE * (absolute_degrees - degrees)
152
+ absolute_decimal_minutes = (
153
+ MINUTES_PER_DEGREE * (absolute_degrees - degrees)
154
+ ).abs
155
+ minutes = decimal_minutes.floor
156
+ seconds = SECONDS_PER_MINUTE * (
157
+ absolute_decimal_minutes - absolute_decimal_minutes.floor
158
+ )
159
+
160
+ Dms.new(sign, degrees, minutes, seconds.to_f.floor(4))
161
+ end
162
+
163
+ def to_hms(hrs)
164
+ absolute_hours = hrs.abs
165
+ hours = absolute_hours.floor
166
+ decimal_minutes = MINUTES_PER_HOUR * (absolute_hours - hours)
167
+ absolute_decimal_minutes = (
168
+ MINUTES_PER_HOUR * (absolute_hours - hours)
169
+ ).abs
170
+ minutes = decimal_minutes.floor
171
+ seconds = SECONDS_PER_MINUTE * (
172
+ absolute_decimal_minutes - absolute_decimal_minutes.floor
173
+ )
174
+
175
+ Hms.new(hours, minutes, seconds.to_f.floor(4))
40
176
  end
41
177
  end
42
178
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Dms
5
+ attr_reader :sign, :degrees, :minutes, :seconds
6
+
7
+ def initialize(sign, degrees, minutes, seconds)
8
+ @sign = sign
9
+ @degrees = degrees
10
+ @minutes = minutes
11
+ @seconds = seconds
12
+ end
13
+
14
+ def format
15
+ "#{sign}#{degrees}° #{minutes}′ #{seconds}″"
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Hms
5
+ attr_reader :hours, :minutes, :seconds
6
+
7
+ def initialize(hours, minutes, seconds)
8
+ @hours = hours
9
+ @minutes = minutes
10
+ @seconds = seconds
11
+ end
12
+
13
+ def format
14
+ "#{hours}h #{minutes}m #{seconds}s"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,226 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Sun
5
+ SEMI_MAJOR_AXIS_IN_METERS = 149_598_500_000
6
+ ANGULAR_DIAMETER = Angle.as_degrees(0.533128)
7
+ INTERPOLATION_FACTOR = BigDecimal("24.07")
8
+
9
+ # Source:
10
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
11
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
12
+ # Edition: Cambridge University Press
13
+ # Chapter: 51 - The equation of time
14
+
15
+ # @param date [Date] Requested date
16
+ # @return [Integer] Equation of time in seconds
17
+ def self.equation_of_time(date:)
18
+ noon = Time.utc(date.year, date.month, date.day, 12)
19
+ epoch_at_noon = Epoch.from_time(noon)
20
+ sun_at_noon = new(epoch: epoch_at_noon)
21
+ equatorial_hours = sun_at_noon
22
+ .ecliptic_coordinates
23
+ .to_equatorial(epoch: epoch_at_noon)
24
+ .right_ascension
25
+ .hours
26
+ gst = GreenwichSiderealTime
27
+ .new(date: date, time: equatorial_hours)
28
+ .to_utc
29
+
30
+ (noon - gst).to_i
31
+ end
32
+
33
+ # Source:
34
+ # Title: Celestial Calculations
35
+ # Author: J. L. Lawrence
36
+ # Edition: MIT Press
37
+ # Chapter: 6 - The Sun
38
+
39
+ # @param epoch [Numeric] Considered epoch, in Julian days
40
+ def initialize(epoch:)
41
+ @epoch = epoch
42
+ end
43
+
44
+ # @return [Astronoby::Coordinates::Ecliptic] Sun's ecliptic coordinates
45
+ def ecliptic_coordinates
46
+ Coordinates::Ecliptic.new(
47
+ latitude: Angle.zero,
48
+ longitude: Angle.as_degrees(
49
+ (true_anomaly + longitude_at_perigee).degrees % 360
50
+ )
51
+ )
52
+ end
53
+
54
+ # Computes the Sun's horizontal coordinates
55
+ #
56
+ # @param latitude [Astronoby::Angle] Latitude of the observer
57
+ # @param longitude [Astronoby::Angle] Longitude of the observer
58
+ # @return [Astronoby::Coordinates::Horizontal] Sun's horizontal coordinates
59
+ def horizontal_coordinates(latitude:, longitude:)
60
+ time = Epoch.to_utc(@epoch)
61
+
62
+ ecliptic_coordinates
63
+ .to_equatorial(epoch: @epoch)
64
+ .to_horizontal(time: time, latitude: latitude, longitude: longitude)
65
+ end
66
+
67
+ # @param observer [Astronoby::Observer] Observer of the event
68
+ # @return [Time] Time of sunrise
69
+ def rising_time(observer:)
70
+ event_date = Epoch.to_utc(@epoch).to_date
71
+ lst1 = event_local_sidereal_time_for_date(event_date, observer, :rising)
72
+ next_day = event_date.next_day(1)
73
+ lst2 = event_local_sidereal_time_for_date(next_day, observer, :rising)
74
+ time = (INTERPOLATION_FACTOR * lst1) / (INTERPOLATION_FACTOR + lst1 - lst2)
75
+
76
+ LocalSiderealTime.new(
77
+ date: event_date,
78
+ time: time,
79
+ longitude: observer.longitude
80
+ ).to_gst.to_utc
81
+ end
82
+
83
+ # @param observer [Astronoby::Observer] Observer of the event
84
+ # @return [Astronoby::Angle, nil] Azimuth of sunrise
85
+ def rising_azimuth(observer:)
86
+ equatorial_coordinates = ecliptic_coordinates.to_equatorial(epoch: @epoch)
87
+ Body.new(equatorial_coordinates).rising_azimuth(
88
+ latitude: observer.latitude,
89
+ vertical_shift: vertical_shift
90
+ )
91
+ end
92
+
93
+ # @param observer [Astronoby::Observer] Observer of the event
94
+ # @return [Time] Time of sunset
95
+ def setting_time(observer:)
96
+ event_date = Epoch.to_utc(@epoch).to_date
97
+ lst1 = event_local_sidereal_time_for_date(event_date, observer, :setting)
98
+ next_day = event_date.next_day(1)
99
+ lst2 = event_local_sidereal_time_for_date(next_day, observer, :setting)
100
+ time = (INTERPOLATION_FACTOR * lst1) / (INTERPOLATION_FACTOR + lst1 - lst2)
101
+
102
+ LocalSiderealTime.new(
103
+ date: event_date,
104
+ time: time,
105
+ longitude: observer.longitude
106
+ ).to_gst.to_utc
107
+ end
108
+
109
+ # @param observer [Astronoby::Observer] Observer of the event
110
+ # @return [Astronoby::Angle, nil] Azimuth of sunset
111
+ def setting_azimuth(observer:)
112
+ equatorial_coordinates = ecliptic_coordinates.to_equatorial(epoch: @epoch)
113
+ Body.new(equatorial_coordinates).setting_azimuth(
114
+ latitude: observer.latitude,
115
+ vertical_shift: vertical_shift
116
+ )
117
+ end
118
+
119
+ # @return [Numeric] Earth-Sun distance in meters
120
+ def earth_distance
121
+ SEMI_MAJOR_AXIS_IN_METERS / distance_angular_size_factor
122
+ end
123
+
124
+ # @return [Astronoby::Angle] Apparent Sun's angular size
125
+ def angular_size
126
+ Angle.as_degrees(ANGULAR_DIAMETER.degrees * distance_angular_size_factor)
127
+ end
128
+
129
+ # @return [Astronoby::Angle] Sun's true anomaly
130
+ def true_anomaly
131
+ eccentric_anomaly = Util::Astrodynamics.eccentric_anomaly_newton_raphson(
132
+ mean_anomaly,
133
+ orbital_eccentricity.degrees,
134
+ 2e-06,
135
+ 10
136
+ )
137
+
138
+ tan = Math.sqrt(
139
+ (1 + orbital_eccentricity.degrees) / (1 - orbital_eccentricity.degrees)
140
+ ) * Math.tan(eccentric_anomaly.radians / 2)
141
+
142
+ Angle.as_degrees((Angle.atan(tan).degrees * 2) % 360)
143
+ end
144
+
145
+ # @return [Astronoby::Angle] Sun's longitude at perigee
146
+ def longitude_at_perigee
147
+ Angle.as_degrees(
148
+ (281.2208444 + 1.719175 * centuries + 0.000452778 * centuries**2) % 360
149
+ )
150
+ end
151
+
152
+ # @return [Astronoby::Angle] Sun's orbital eccentricity
153
+ def orbital_eccentricity
154
+ Angle.as_degrees(
155
+ (0.01675104 - 0.0000418 * centuries - 0.000000126 * centuries**2) % 360
156
+ )
157
+ end
158
+
159
+ private
160
+
161
+ def mean_anomaly
162
+ Angle.as_degrees(
163
+ (longitude_at_base_epoch - longitude_at_perigee).degrees % 360
164
+ )
165
+ end
166
+
167
+ def days_since_epoch
168
+ Epoch::DEFAULT_EPOCH - @epoch
169
+ end
170
+
171
+ def centuries
172
+ @centuries ||= (@epoch - Epoch::J1900) / Epoch::DAYS_PER_JULIAN_CENTURY
173
+ end
174
+
175
+ def longitude_at_base_epoch
176
+ Angle.as_degrees(
177
+ (279.6966778 + 36000.76892 * centuries + 0.0003025 * centuries**2) % 360
178
+ )
179
+ end
180
+
181
+ def distance_angular_size_factor
182
+ term1 = 1 + orbital_eccentricity.degrees * true_anomaly.cos
183
+ term2 = 1 - orbital_eccentricity.degrees**2
184
+
185
+ term1 / term2
186
+ end
187
+
188
+ def event_local_sidereal_time_for_date(date, observer, event)
189
+ midnight_utc = Time.utc(date.year, date.month, date.day)
190
+ epoch = Epoch.from_time(midnight_utc)
191
+ sun_at_midnight = self.class.new(epoch: epoch)
192
+ shift = Body::DEFAULT_REFRACTION_VERTICAL_SHIFT +
193
+ GeocentricParallax.angle(distance: sun_at_midnight.earth_distance) +
194
+ Angle.as_degrees(sun_at_midnight.angular_size.degrees / 2)
195
+ ecliptic_coordinates = sun_at_midnight.ecliptic_coordinates
196
+ equatorial_coordinates = ecliptic_coordinates.to_equatorial(epoch: epoch)
197
+
198
+ event_time = if event == :rising
199
+ Body.new(equatorial_coordinates).rising_time(
200
+ latitude: observer.latitude,
201
+ longitude: observer.longitude,
202
+ date: midnight_utc.to_date,
203
+ vertical_shift: shift
204
+ )
205
+ else
206
+ Body.new(equatorial_coordinates).setting_time(
207
+ latitude: observer.latitude,
208
+ longitude: observer.longitude,
209
+ date: midnight_utc.to_date,
210
+ vertical_shift: shift
211
+ )
212
+ end
213
+
214
+ GreenwichSiderealTime
215
+ .from_utc(event_time.utc)
216
+ .to_lst(longitude: observer.longitude)
217
+ .time
218
+ end
219
+
220
+ def vertical_shift
221
+ Astronoby::Body::DEFAULT_REFRACTION_VERTICAL_SHIFT +
222
+ Astronoby::GeocentricParallax.angle(distance: earth_distance) +
223
+ Astronoby::Angle.as_degrees(angular_size.degrees / 2)
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Body
5
+ DEFAULT_REFRACTION_VERTICAL_SHIFT = Angle.as_dms(0, 34, 0)
6
+ RISING_SETTING_HOUR_ANGLE_RATIO_RANGE = (-1..1)
7
+
8
+ def initialize(equatorial_coordinates)
9
+ @equatorial_coordinates = equatorial_coordinates
10
+ end
11
+
12
+ # Source:
13
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
14
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
15
+ # Edition: Cambridge University Press
16
+ # Chapter: 33 - Rising and setting
17
+
18
+ # @param latitude [Astronoby::Angle] Latitude of the observer
19
+ # @param longitude [Astronoby::Angle] Longitude of the observer
20
+ # @param date [Date] Date of the event
21
+ # @param apparent [Boolean] Compute apparent or true data
22
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
23
+ # @return [Time, nil] Sunrise time
24
+ def rising_time(
25
+ latitude:,
26
+ longitude:,
27
+ date:,
28
+ apparent: true,
29
+ vertical_shift: nil
30
+ )
31
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
32
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
33
+
34
+ hour_angle = Angle.acos(time_ratio)
35
+ local_sidereal_time = LocalSiderealTime.new(
36
+ date: date,
37
+ time: right_ascension.hours - hour_angle.hours,
38
+ longitude: longitude
39
+ )
40
+
41
+ local_sidereal_time.to_gst.to_utc
42
+ end
43
+
44
+ # Source:
45
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
46
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
47
+ # Edition: Cambridge University Press
48
+ # Chapter: 33 - Rising and setting
49
+
50
+ # @param latitude [Astronoby::Angle] Latitude of the observer
51
+ # @param apparent [Boolean] Compute apparent or true data
52
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
53
+ # @return [Astronoby::Angle, nil] Sunrise azimuth
54
+ def rising_azimuth(latitude:, apparent: true, vertical_shift: nil)
55
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
56
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
57
+
58
+ azimuth_ratio = azimuth_ratio(latitude, apparent, vertical_shift)
59
+
60
+ Angle.acos(azimuth_ratio)
61
+ end
62
+
63
+ # Source:
64
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
65
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
66
+ # Edition: Cambridge University Press
67
+ # Chapter: 33 - Rising and setting
68
+
69
+ # @param latitude [Astronoby::Angle] Latitude of the observer
70
+ # @param longitude [Astronoby::Angle] Longitude of the observer
71
+ # @param date [Date] Date of the event
72
+ # @param apparent [Boolean] Compute apparent or true data
73
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
74
+ # @return [Time, nil] Sunset time
75
+ def setting_time(
76
+ latitude:,
77
+ longitude:,
78
+ date:,
79
+ apparent: true,
80
+ vertical_shift: nil
81
+ )
82
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
83
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
84
+
85
+ hour_angle = Angle.acos(time_ratio)
86
+ local_sidereal_time = LocalSiderealTime.new(
87
+ date: date,
88
+ time: right_ascension.hours + hour_angle.hours,
89
+ longitude: longitude
90
+ )
91
+
92
+ local_sidereal_time.to_gst.to_utc
93
+ end
94
+
95
+ # Source:
96
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
97
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
98
+ # Edition: Cambridge University Press
99
+ # Chapter: 33 - Rising and setting
100
+
101
+ # @param latitude [Astronoby::Angle] Latitude of the observer
102
+ # @param apparent [Boolean] Compute apparent or true data
103
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
104
+ # @return [Astronoby::Angle, nil] Sunset azimuth
105
+ def setting_azimuth(latitude:, apparent: true, vertical_shift: nil)
106
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
107
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
108
+
109
+ azimuth_ratio = azimuth_ratio(latitude, apparent, vertical_shift)
110
+
111
+ Angle.as_degrees(360 - Angle.acos(azimuth_ratio).degrees)
112
+ end
113
+
114
+ private
115
+
116
+ def time_ratio(latitude, apparent, vertical_shift)
117
+ shift = if vertical_shift
118
+ vertical_shift
119
+ elsif apparent
120
+ DEFAULT_REFRACTION_VERTICAL_SHIFT
121
+ else
122
+ Angle.zero
123
+ end
124
+
125
+ term1 = shift.sin + latitude.sin * declination.sin
126
+ term2 = latitude.cos * declination.cos
127
+
128
+ -term1 / term2
129
+ end
130
+
131
+ def azimuth_ratio(latitude, apparent, vertical_shift)
132
+ shift = if vertical_shift
133
+ vertical_shift
134
+ elsif apparent
135
+ DEFAULT_REFRACTION_VERTICAL_SHIFT
136
+ else
137
+ Angle.zero
138
+ end
139
+
140
+ (declination.sin + shift.sin * latitude.cos) / (shift.cos * latitude.cos)
141
+ end
142
+
143
+ def azimuth_component(latitude)
144
+ declination.sin / latitude.cos
145
+ end
146
+
147
+ def right_ascension
148
+ @equatorial_coordinates.right_ascension
149
+ end
150
+
151
+ def declination
152
+ @equatorial_coordinates.declination
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Coordinates
5
+ class Ecliptic
6
+ attr_reader :latitude, :longitude
7
+
8
+ def initialize(latitude:, longitude:)
9
+ @latitude = latitude
10
+ @longitude = longitude
11
+ end
12
+
13
+ # Source:
14
+ # Title: Celestial Calculations
15
+ # Author: J. L. Lawrence
16
+ # Edition: MIT Press
17
+ # Chapter: 4 - Orbits and Coordinate Systems
18
+ def to_equatorial(epoch:)
19
+ mean_obliquity = MeanObliquity.for_epoch(epoch)
20
+
21
+ y = Angle.as_radians(
22
+ @longitude.sin * mean_obliquity.cos -
23
+ @latitude.tan * mean_obliquity.sin
24
+ )
25
+ x = Angle.as_radians(@longitude.cos)
26
+ r = Angle.atan(y.radians / x.radians)
27
+ right_ascension = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
28
+
29
+ declination = Angle.asin(
30
+ @latitude.sin * mean_obliquity.cos +
31
+ @latitude.cos * mean_obliquity.sin * @longitude.sin
32
+ )
33
+
34
+ Equatorial.new(
35
+ right_ascension: right_ascension,
36
+ declination: declination,
37
+ epoch: epoch
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end