astronoby 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,30 +4,63 @@ module Astronoby
4
4
  class Sun
5
5
  SEMI_MAJOR_AXIS_IN_METERS = 149_598_500_000
6
6
  ANGULAR_DIAMETER = Angle.from_degrees(0.533128)
7
- INTERPOLATION_FACTOR = BigDecimal("24.07")
7
+ INTERPOLATION_FACTOR = 24.07
8
+
9
+ TWILIGHTS = [
10
+ CIVIL = :civil,
11
+ NAUTICAL = :nautical,
12
+ ASTRONOMICAL = :astronomical
13
+ ].freeze
14
+
15
+ TWILIGHT_ANGLES = {
16
+ CIVIL => Angle.from_degrees(96),
17
+ NAUTICAL => Angle.from_degrees(102),
18
+ ASTRONOMICAL => Angle.from_degrees(108)
19
+ }.freeze
20
+
21
+ PERIODS_OF_THE_DAY = [
22
+ MORNING = :morning,
23
+ EVENING = :evening
24
+ ].freeze
25
+
26
+ attr_reader :time
8
27
 
9
28
  # 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
29
+ # Title: Astronomical Algorithms
30
+ # Author: Jean Meeus
31
+ # Edition: 2nd edition
32
+ # Chapter: 28 - Equation of Time
14
33
 
15
- # @param date [Date] Requested date
34
+ # @param date_or_time [Date, Time] Requested date
16
35
  # @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
36
+ def self.equation_of_time(date_or_time:)
37
+ noon = Time.utc(date_or_time.year, date_or_time.month, date_or_time.day, 12)
38
+ time = date_or_time.is_a?(Time) ? date_or_time : noon
39
+ epoch = Epoch.from_time(time)
40
+ sun = new(time: time)
41
+ right_ascension = sun
22
42
  .apparent_ecliptic_coordinates
23
- .to_apparent_equatorial(epoch: epoch_at_noon)
43
+ .to_apparent_equatorial(epoch: epoch)
24
44
  .right_ascension
25
- .hours
26
- gst = GreenwichSiderealTime
27
- .new(date: date, time: equatorial_hours)
28
- .to_utc
29
-
30
- (noon - gst).to_i
45
+ t = (epoch - Epoch::J2000) / Constants::DAYS_PER_JULIAN_MILLENIA
46
+ l0 = (280.4664567 +
47
+ 360_007.6982779 * t +
48
+ 0.03032028 * t**2 +
49
+ t**3 / 49_931 -
50
+ t**4 / 15_300 -
51
+ t**5 / 2_000_000) % Constants::DEGREES_PER_CIRCLE
52
+ nutation = Nutation.for_ecliptic_longitude(epoch: epoch)
53
+ obliquity = TrueObliquity.for_epoch(epoch)
54
+
55
+ (
56
+ Angle
57
+ .from_degrees(
58
+ l0 -
59
+ Constants::EQUATION_OF_TIME_CONSTANT -
60
+ right_ascension.degrees +
61
+ nutation.degrees * obliquity.cos
62
+ ).hours * Constants::SECONDS_PER_HOUR
63
+ ).round
31
64
  end
32
65
 
33
66
  # Source:
@@ -36,9 +69,13 @@ module Astronoby
36
69
  # Edition: MIT Press
37
70
  # Chapter: 6 - The Sun
38
71
 
39
- # @param epoch [Numeric] Considered epoch, in Julian days
40
- def initialize(epoch:)
41
- @epoch = epoch
72
+ # @param time [Time] Considered time
73
+ def initialize(time:)
74
+ @time = time
75
+ end
76
+
77
+ def epoch
78
+ @epoch ||= Epoch.from_time(@time)
42
79
  end
43
80
 
44
81
  def true_ecliptic_coordinates
@@ -49,10 +86,10 @@ module Astronoby
49
86
  end
50
87
 
51
88
  def apparent_ecliptic_coordinates
52
- nutation = Nutation.for_ecliptic_longitude(epoch: @epoch)
89
+ nutation = Nutation.for_ecliptic_longitude(epoch: epoch)
53
90
  longitude_with_aberration = Aberration.for_ecliptic_coordinates(
54
91
  coordinates: true_ecliptic_coordinates,
55
- epoch: @epoch
92
+ epoch: epoch
56
93
  ).longitude
57
94
  apparent_longitude = nutation + longitude_with_aberration
58
95
 
@@ -68,65 +105,55 @@ module Astronoby
68
105
  # @param longitude [Astronoby::Angle] Longitude of the observer
69
106
  # @return [Astronoby::Coordinates::Horizontal] Sun's horizontal coordinates
70
107
  def horizontal_coordinates(latitude:, longitude:)
71
- time = Epoch.to_utc(@epoch)
72
-
73
108
  apparent_ecliptic_coordinates
74
- .to_apparent_equatorial(epoch: @epoch)
75
- .to_horizontal(time: time, latitude: latitude, longitude: longitude)
76
- end
77
-
78
- # @param observer [Astronoby::Observer] Observer of the event
79
- # @return [Time] Time of sunrise
80
- def rising_time(observer:)
81
- event_date = Epoch.to_utc(@epoch).to_date
82
- lst1 = event_local_sidereal_time_for_date(event_date, observer, :rising)
83
- next_day = event_date.next_day(1)
84
- lst2 = event_local_sidereal_time_for_date(next_day, observer, :rising)
85
- time = (INTERPOLATION_FACTOR * lst1) / (INTERPOLATION_FACTOR + lst1 - lst2)
86
-
87
- LocalSiderealTime.new(
88
- date: event_date,
89
- time: time,
90
- longitude: observer.longitude
91
- ).to_gst.to_utc
109
+ .to_apparent_equatorial(epoch: epoch)
110
+ .to_horizontal(time: @time, latitude: latitude, longitude: longitude)
92
111
  end
93
112
 
94
113
  # @param observer [Astronoby::Observer] Observer of the event
95
- # @return [Astronoby::Angle, nil] Azimuth of sunrise
96
- def rising_azimuth(observer:)
97
- equatorial_coordinates = apparent_ecliptic_coordinates
98
- .to_apparent_equatorial(epoch: @epoch)
99
- Body.new(equatorial_coordinates).rising_azimuth(
100
- latitude: observer.latitude,
101
- vertical_shift: vertical_shift
114
+ # @return [Astronoby::Events::ObservationEvents] Sun's observation events
115
+ def observation_events(observer:)
116
+ today = @time.to_date
117
+ leap_seconds = Util::Time.terrestrial_universal_time_delta(today)
118
+ yesterday = today.prev_day
119
+ yesterday_midnight_terrestrial_time =
120
+ Time.utc(yesterday.year, yesterday.month, yesterday.day) - leap_seconds
121
+ yesterday_epoch = Epoch.from_time(yesterday_midnight_terrestrial_time)
122
+ today_midnight_terrestrial_time =
123
+ Time.utc(today.year, today.month, today.day) - leap_seconds
124
+ today_epoch = Epoch.from_time(today_midnight_terrestrial_time)
125
+ tomorrow = today.next_day
126
+ tomorrow_midnight_terrestrial_time =
127
+ Time.utc(tomorrow.year, tomorrow.month, tomorrow.day) - leap_seconds
128
+ tomorrow_epoch = Epoch.from_time(tomorrow_midnight_terrestrial_time)
129
+
130
+ coordinates_of_the_previous_day = self.class
131
+ .new(time: yesterday_midnight_terrestrial_time)
132
+ .apparent_ecliptic_coordinates
133
+ .to_apparent_equatorial(epoch: yesterday_epoch)
134
+ coordinates_of_the_day = self.class
135
+ .new(time: today_midnight_terrestrial_time)
136
+ .apparent_ecliptic_coordinates
137
+ .to_apparent_equatorial(epoch: today_epoch)
138
+ coordinates_of_the_next_day = self.class
139
+ .new(time: tomorrow_midnight_terrestrial_time)
140
+ .apparent_ecliptic_coordinates
141
+ .to_apparent_equatorial(epoch: tomorrow_epoch)
142
+
143
+ Events::ObservationEvents.new(
144
+ observer: observer,
145
+ date: today,
146
+ coordinates_of_the_previous_day: coordinates_of_the_previous_day,
147
+ coordinates_of_the_day: coordinates_of_the_day,
148
+ coordinates_of_the_next_day: coordinates_of_the_next_day,
149
+ additional_altitude: Angle.from_degrees(angular_size.degrees / 2)
102
150
  )
103
151
  end
104
152
 
105
- # @param observer [Astronoby::Observer] Observer of the event
106
- # @return [Time] Time of sunset
107
- def setting_time(observer:)
108
- event_date = Epoch.to_utc(@epoch).to_date
109
- lst1 = event_local_sidereal_time_for_date(event_date, observer, :setting)
110
- next_day = event_date.next_day(1)
111
- lst2 = event_local_sidereal_time_for_date(next_day, observer, :setting)
112
- time = (INTERPOLATION_FACTOR * lst1) / (INTERPOLATION_FACTOR + lst1 - lst2)
113
-
114
- LocalSiderealTime.new(
115
- date: event_date,
116
- time: time,
117
- longitude: observer.longitude
118
- ).to_gst.to_utc
119
- end
120
-
121
- # @param observer [Astronoby::Observer] Observer of the event
122
- # @return [Astronoby::Angle, nil] Azimuth of sunset
123
- def setting_azimuth(observer:)
124
- equatorial_coordinates = apparent_ecliptic_coordinates
125
- .to_apparent_equatorial(epoch: @epoch)
126
- Body.new(equatorial_coordinates).setting_azimuth(
127
- latitude: observer.latitude,
128
- vertical_shift: vertical_shift
129
- )
153
+ # @param observer [Astronoby::Observer] Observer of the events
154
+ # @return [Astronoby::Events::TwilightEvents] Sun's twilight events
155
+ def twilight_events(observer:)
156
+ Events::TwilightEvents.new(sun: self, observer: observer)
130
157
  end
131
158
 
132
159
  # @return [Numeric] Earth-Sun distance in meters
@@ -154,20 +181,24 @@ module Astronoby
154
181
  (1 + orbital_eccentricity.degrees) / (1 - orbital_eccentricity.degrees)
155
182
  ) * Math.tan(eccentric_anomaly.radians / 2)
156
183
 
157
- Angle.from_degrees((Angle.atan(tan).degrees * 2) % 360)
184
+ Angle.from_degrees(
185
+ (Angle.atan(tan).degrees * 2) % Constants::DEGREES_PER_CIRCLE
186
+ )
158
187
  end
159
188
 
160
189
  # @return [Astronoby::Angle] Sun's longitude at perigee
161
190
  def longitude_at_perigee
162
191
  Angle.from_degrees(
163
- (281.2208444 + 1.719175 * centuries + 0.000452778 * centuries**2) % 360
192
+ (281.2208444 + 1.719175 * centuries + 0.000452778 * centuries**2) %
193
+ Constants::DEGREES_PER_CIRCLE
164
194
  )
165
195
  end
166
196
 
167
197
  # @return [Astronoby::Angle] Sun's orbital eccentricity
168
198
  def orbital_eccentricity
169
199
  Angle.from_degrees(
170
- (0.01675104 - 0.0000418 * centuries - 0.000000126 * centuries**2) % 360
200
+ (0.01675104 - 0.0000418 * centuries - 0.000000126 * centuries**2) %
201
+ Constants::DEGREES_PER_CIRCLE
171
202
  )
172
203
  end
173
204
 
@@ -175,27 +206,30 @@ module Astronoby
175
206
 
176
207
  def true_longitude
177
208
  Angle.from_degrees(
178
- (true_anomaly + longitude_at_perigee).degrees % 360
209
+ (true_anomaly + longitude_at_perigee).degrees %
210
+ Constants::DEGREES_PER_CIRCLE
179
211
  )
180
212
  end
181
213
 
182
214
  def mean_anomaly
183
215
  Angle.from_degrees(
184
- (longitude_at_base_epoch - longitude_at_perigee).degrees % 360
216
+ (longitude_at_base_epoch - longitude_at_perigee).degrees %
217
+ Constants::DEGREES_PER_CIRCLE
185
218
  )
186
219
  end
187
220
 
188
221
  def days_since_epoch
189
- Epoch::DEFAULT_EPOCH - @epoch
222
+ Epoch::DEFAULT_EPOCH - epoch
190
223
  end
191
224
 
192
225
  def centuries
193
- @centuries ||= (@epoch - Epoch::J1900) / Epoch::DAYS_PER_JULIAN_CENTURY
226
+ @centuries ||= (epoch - Epoch::J1900) / Constants::DAYS_PER_JULIAN_CENTURY
194
227
  end
195
228
 
196
229
  def longitude_at_base_epoch
197
230
  Angle.from_degrees(
198
- (279.6966778 + 36000.76892 * centuries + 0.0003025 * centuries**2) % 360
231
+ (279.6966778 + 36000.76892 * centuries + 0.0003025 * centuries**2) %
232
+ Constants::DEGREES_PER_CIRCLE
199
233
  )
200
234
  end
201
235
 
@@ -205,44 +239,5 @@ module Astronoby
205
239
 
206
240
  term1 / term2
207
241
  end
208
-
209
- def event_local_sidereal_time_for_date(date, observer, event)
210
- midnight_utc = Time.utc(date.year, date.month, date.day)
211
- epoch = Epoch.from_time(midnight_utc)
212
- sun_at_midnight = self.class.new(epoch: epoch)
213
- shift = Body::DEFAULT_REFRACTION_VERTICAL_SHIFT +
214
- GeocentricParallax.angle(distance: sun_at_midnight.earth_distance) +
215
- Angle.from_degrees(sun_at_midnight.angular_size.degrees / 2)
216
- ecliptic_coordinates = sun_at_midnight.apparent_ecliptic_coordinates
217
- equatorial_coordinates = ecliptic_coordinates
218
- .to_apparent_equatorial(epoch: epoch)
219
-
220
- event_time = if event == :rising
221
- Body.new(equatorial_coordinates).rising_time(
222
- latitude: observer.latitude,
223
- longitude: observer.longitude,
224
- date: midnight_utc.to_date,
225
- vertical_shift: shift
226
- )
227
- else
228
- Body.new(equatorial_coordinates).setting_time(
229
- latitude: observer.latitude,
230
- longitude: observer.longitude,
231
- date: midnight_utc.to_date,
232
- vertical_shift: shift
233
- )
234
- end
235
-
236
- GreenwichSiderealTime
237
- .from_utc(event_time.utc)
238
- .to_lst(longitude: observer.longitude)
239
- .time
240
- end
241
-
242
- def vertical_shift
243
- Astronoby::Body::DEFAULT_REFRACTION_VERTICAL_SHIFT +
244
- Astronoby::GeocentricParallax.angle(distance: earth_distance) +
245
- Astronoby::Angle.from_degrees(angular_size.degrees / 2)
246
- end
247
242
  end
248
243
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Constants
5
+ DAYS_PER_JULIAN_CENTURY = 36525.0
6
+ DAYS_PER_JULIAN_MILLENIA = DAYS_PER_JULIAN_CENTURY * 10
7
+
8
+ HOURS_PER_DAY = 24.0
9
+ DEGREES_PER_CIRCLE = 360.0
10
+ RADIANS_PER_CIRCLE = 2 * Math::PI
11
+
12
+ SECONDS_PER_MINUTE = 60.0
13
+ MINUTES_PER_HOUR = 60.0
14
+ MINUTES_PER_DEGREE = 60.0
15
+
16
+ SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR
17
+ SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY
18
+ SECONDS_PER_DEGREE = SECONDS_PER_MINUTE * MINUTES_PER_DEGREE
19
+ RADIAN_PER_HOUR = Math::PI / 12.0
20
+
21
+ PI_IN_DEGREES = 180.0
22
+
23
+ EQUATION_OF_TIME_CONSTANT = 0.0057183
24
+ end
25
+ end
@@ -18,17 +18,17 @@ module Astronoby
18
18
 
19
19
  def to_true_equatorial(epoch:)
20
20
  mean_obliquity = MeanObliquity.for_epoch(epoch)
21
- to_equatorial(obliquity: mean_obliquity)
21
+ to_equatorial(obliquity: mean_obliquity, epoch: epoch)
22
22
  end
23
23
 
24
24
  def to_apparent_equatorial(epoch:)
25
25
  apparent_obliquity = TrueObliquity.for_epoch(epoch)
26
- to_equatorial(obliquity: apparent_obliquity)
26
+ to_equatorial(obliquity: apparent_obliquity, epoch: epoch)
27
27
  end
28
28
 
29
29
  private
30
30
 
31
- def to_equatorial(obliquity:)
31
+ def to_equatorial(obliquity:, epoch:)
32
32
  y = Angle.from_radians(
33
33
  @longitude.sin * obliquity.cos -
34
34
  @latitude.tan * obliquity.sin
@@ -45,7 +45,7 @@ module Astronoby
45
45
  Equatorial.new(
46
46
  right_ascension: right_ascension,
47
47
  declination: declination,
48
- epoch: @epoch
48
+ epoch: epoch
49
49
  )
50
50
  end
51
51
  end
@@ -23,7 +23,7 @@ module Astronoby
23
23
  .to_lst(longitude: longitude)
24
24
 
25
25
  ha = (lst.time - @right_ascension.hours)
26
- ha += 24 if ha.negative?
26
+ ha += Constants::HOURS_PER_DAY if ha.negative?
27
27
 
28
28
  Angle.from_hours(ha)
29
29
  end
@@ -39,7 +39,8 @@ module Astronoby
39
39
  azimuth = Angle.acos(t2)
40
40
 
41
41
  if ha.sin.positive?
42
- azimuth = Angle.from_degrees(BigDecimal("360") - azimuth.degrees)
42
+ azimuth =
43
+ Angle.from_degrees(Constants::DEGREES_PER_CIRCLE - azimuth.degrees)
43
44
  end
44
45
 
45
46
  Horizontal.new(
@@ -31,7 +31,7 @@ module Astronoby
31
31
 
32
32
  if @azimuth.sin.positive?
33
33
  hour_angle_degrees = Angle
34
- .from_degrees(BigDecimal("360") - hour_angle_degrees)
34
+ .from_degrees(Constants::DEGREES_PER_CIRCLE - hour_angle_degrees)
35
35
  .degrees
36
36
  end
37
37
 
@@ -40,7 +40,11 @@ module Astronoby
40
40
  .from_utc(time.utc)
41
41
  .to_lst(longitude: @longitude)
42
42
  right_ascension_decimal = lst.time - hour_angle_hours
43
- right_ascension_decimal += 24 if right_ascension_decimal.negative?
43
+
44
+ if right_ascension_decimal.negative?
45
+ right_ascension_decimal += Constants::HOURS_PER_DAY
46
+ end
47
+
44
48
  right_ascension = Angle.from_hours(right_ascension_decimal)
45
49
 
46
50
  Equatorial.new(
@@ -9,8 +9,6 @@ module Astronoby
9
9
  J2000 = 2451545.0
10
10
 
11
11
  DEFAULT_EPOCH = J2000
12
- DAYS_PER_JULIAN_CENTURY = 36525.0
13
-
14
12
  JULIAN_DAY_NUMBER_OFFSET = 0.5
15
13
 
16
14
  def self.from_time(time)
@@ -101,7 +101,7 @@ module Astronoby
101
101
  end
102
102
 
103
103
  def compute
104
- t = (julian_day - Epoch::J2000) / Epoch::DAYS_PER_JULIAN_CENTURY
104
+ t = (julian_day - Epoch::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
105
105
  w = Angle.from_degrees(35999.373 * t) - Angle.from_degrees(2.47)
106
106
  delta = 1 +
107
107
  0.0334 * w.cos +
@@ -130,7 +130,8 @@ module Astronoby
130
130
  end
131
131
 
132
132
  def correction(epoch)
133
- sun = Sun.new(epoch: epoch)
133
+ time = Epoch.to_utc(epoch)
134
+ sun = Sun.new(time: time)
134
135
  longitude = sun.apparent_ecliptic_coordinates.longitude
135
136
 
136
137
  58 * Angle.from_degrees(@event * 90 - longitude.degrees).sin