astronoby 0.3.0 → 0.4.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,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