astronoby 0.3.0 → 0.5.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/Gemfile.lock +19 -17
  4. data/README.md +195 -25
  5. data/UPGRADING.md +72 -0
  6. data/lib/astronoby/aberration.rb +7 -5
  7. data/lib/astronoby/angle.rb +26 -34
  8. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +143 -0
  9. data/lib/astronoby/astronomical_models/moon_phases_periodic_terms.rb +249 -0
  10. data/lib/astronoby/bodies/moon.rb +335 -0
  11. data/lib/astronoby/bodies/sun.rb +129 -132
  12. data/lib/astronoby/constants.rb +31 -0
  13. data/lib/astronoby/coordinates/ecliptic.rb +4 -4
  14. data/lib/astronoby/coordinates/equatorial.rb +7 -5
  15. data/lib/astronoby/coordinates/horizontal.rb +24 -12
  16. data/lib/astronoby/distance.rb +83 -0
  17. data/lib/astronoby/epoch.rb +0 -2
  18. data/lib/astronoby/equinox_solstice.rb +3 -2
  19. data/lib/astronoby/events/moon_phases.rb +143 -0
  20. data/lib/astronoby/events/observation_events.rb +259 -0
  21. data/lib/astronoby/events/rise_transit_set_iteration.rb +215 -0
  22. data/lib/astronoby/events/twilight_events.rb +121 -0
  23. data/lib/astronoby/geocentric_parallax.rb +36 -56
  24. data/lib/astronoby/mean_obliquity.rb +2 -2
  25. data/lib/astronoby/moon_phase.rb +43 -0
  26. data/lib/astronoby/nutation.rb +5 -3
  27. data/lib/astronoby/observer.rb +39 -18
  28. data/lib/astronoby/precession.rb +1 -1
  29. data/lib/astronoby/refraction.rb +8 -10
  30. data/lib/astronoby/time/greenwich_sidereal_time.rb +18 -29
  31. data/lib/astronoby/time/local_sidereal_time.rb +4 -4
  32. data/lib/astronoby/util/maths.rb +72 -0
  33. data/lib/astronoby/util/time.rb +88 -0
  34. data/lib/astronoby/util/trigonometry.rb +4 -4
  35. data/lib/astronoby/version.rb +1 -1
  36. data/lib/astronoby.rb +12 -1
  37. metadata +15 -4
  38. data/lib/astronoby/body.rb +0 -155
@@ -0,0 +1,31 @@
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
+
25
+ KILOMETER_IN_METERS = 1_000
26
+ ASTRONOMICAL_UNIT_IN_METERS = 149_597_870_700
27
+ EARTH_EQUATORIAL_RADIUS_IN_METERS = 6378140
28
+
29
+ EARTH_FLATTENING_CORRECTION = 0.996647
30
+ end
31
+ 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,12 +23,14 @@ 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
30
30
 
31
- def to_horizontal(time:, latitude:, longitude:)
31
+ def to_horizontal(time:, observer:)
32
+ latitude = observer.latitude
33
+ longitude = observer.longitude
32
34
  ha = @hour_angle || compute_hour_angle(time: time, longitude: longitude)
33
35
  t0 = @declination.sin * latitude.sin +
34
36
  @declination.cos * latitude.cos * ha.cos
@@ -39,14 +41,14 @@ module Astronoby
39
41
  azimuth = Angle.acos(t2)
40
42
 
41
43
  if ha.sin.positive?
42
- azimuth = Angle.from_degrees(BigDecimal("360") - azimuth.degrees)
44
+ azimuth =
45
+ Angle.from_degrees(Constants::DEGREES_PER_CIRCLE - azimuth.degrees)
43
46
  end
44
47
 
45
48
  Horizontal.new(
46
49
  azimuth: azimuth,
47
50
  altitude: altitude,
48
- latitude: latitude,
49
- longitude: longitude
51
+ observer: observer
50
52
  )
51
53
  end
52
54
 
@@ -3,44 +3,46 @@
3
3
  module Astronoby
4
4
  module Coordinates
5
5
  class Horizontal
6
- attr_reader :azimuth, :altitude, :latitude, :longitude
6
+ attr_reader :azimuth, :altitude, :observer
7
7
 
8
8
  def initialize(
9
9
  azimuth:,
10
10
  altitude:,
11
- latitude:,
12
- longitude:
11
+ observer:
13
12
  )
14
13
  @azimuth = azimuth
15
14
  @altitude = altitude
16
- @latitude = latitude
17
- @longitude = longitude
15
+ @observer = observer
18
16
  end
19
17
 
20
18
  def to_equatorial(time:)
21
- t0 = @altitude.sin * @latitude.sin +
22
- @altitude.cos * @latitude.cos * @azimuth.cos
19
+ t0 = @altitude.sin * latitude.sin +
20
+ @altitude.cos * latitude.cos * @azimuth.cos
23
21
 
24
22
  declination = Angle.asin(t0)
25
23
 
26
- t1 = @altitude.sin - @latitude.sin * declination.sin
24
+ t1 = @altitude.sin - latitude.sin * declination.sin
27
25
 
28
26
  hour_angle_degrees = Angle
29
- .acos(t1 / (@latitude.cos * declination.cos))
27
+ .acos(t1 / (latitude.cos * declination.cos))
30
28
  .degrees
31
29
 
32
30
  if @azimuth.sin.positive?
33
31
  hour_angle_degrees = Angle
34
- .from_degrees(BigDecimal("360") - hour_angle_degrees)
32
+ .from_degrees(Constants::DEGREES_PER_CIRCLE - hour_angle_degrees)
35
33
  .degrees
36
34
  end
37
35
 
38
36
  hour_angle_hours = Angle.from_degrees(hour_angle_degrees).hours
39
37
  lst = GreenwichSiderealTime
40
38
  .from_utc(time.utc)
41
- .to_lst(longitude: @longitude)
39
+ .to_lst(longitude: longitude)
42
40
  right_ascension_decimal = lst.time - hour_angle_hours
43
- right_ascension_decimal += 24 if right_ascension_decimal.negative?
41
+
42
+ if right_ascension_decimal.negative?
43
+ right_ascension_decimal += Constants::HOURS_PER_DAY
44
+ end
45
+
44
46
  right_ascension = Angle.from_hours(right_ascension_decimal)
45
47
 
46
48
  Equatorial.new(
@@ -48,6 +50,16 @@ module Astronoby
48
50
  declination: declination
49
51
  )
50
52
  end
53
+
54
+ private
55
+
56
+ def latitude
57
+ @observer.latitude
58
+ end
59
+
60
+ def longitude
61
+ @observer.longitude
62
+ end
51
63
  end
52
64
  end
53
65
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Distance
5
+ include Comparable
6
+
7
+ class << self
8
+ def zero
9
+ new(0)
10
+ end
11
+
12
+ def from_meters(meters)
13
+ new(meters)
14
+ end
15
+ alias_method :from_m, :from_meters
16
+
17
+ def from_kilometers(kilometers)
18
+ meters = kilometers * Constants::KILOMETER_IN_METERS
19
+ from_meters(meters)
20
+ end
21
+ alias_method :from_km, :from_kilometers
22
+
23
+ def from_astronomical_units(astronomical_units)
24
+ meters = astronomical_units * Constants::ASTRONOMICAL_UNIT_IN_METERS
25
+ from_meters(meters)
26
+ end
27
+ alias_method :from_au, :from_astronomical_units
28
+ end
29
+
30
+ attr_reader :meters
31
+ alias_method :m, :meters
32
+
33
+ def initialize(meters)
34
+ @meters = meters
35
+ freeze
36
+ end
37
+
38
+ def kilometers
39
+ @meters / Constants::KILOMETER_IN_METERS.to_f
40
+ end
41
+ alias_method :km, :kilometers
42
+
43
+ def astronomical_units
44
+ @meters / Constants::ASTRONOMICAL_UNIT_IN_METERS.to_f
45
+ end
46
+ alias_method :au, :astronomical_units
47
+
48
+ def +(other)
49
+ self.class.from_meters(meters + other.meters)
50
+ end
51
+
52
+ def -(other)
53
+ self.class.from_meters(@meters - other.meters)
54
+ end
55
+
56
+ def -@
57
+ self.class.from_meters(-@meters)
58
+ end
59
+
60
+ def positive?
61
+ meters > 0
62
+ end
63
+
64
+ def negative?
65
+ meters < 0
66
+ end
67
+
68
+ def zero?
69
+ meters.zero?
70
+ end
71
+
72
+ def hash
73
+ [meters, self.class].hash
74
+ end
75
+
76
+ def <=>(other)
77
+ return unless other.is_a?(self.class)
78
+
79
+ meters <=> other.meters
80
+ end
81
+ alias_method :eql?, :==
82
+ end
83
+ end
@@ -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
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Events
5
+ class MoonPhases
6
+ BASE_YEAR = 2000
7
+
8
+ # Source:
9
+ # Title: Astronomical Algorithms
10
+ # Author: Jean Meeus
11
+ # Edition: 2nd edition
12
+ # Chapter: 49 - Phases of the Moon
13
+
14
+ # @param year [Integer] Requested year
15
+ # @param month [Integer] Requested month
16
+ # @return [Array<Astronoby::MoonPhase>] List of Moon phases
17
+ def self.phases_for(year:, month:)
18
+ [
19
+ MoonPhase.first_quarter(new(year, month, :first_quarter, -0.75).time),
20
+ MoonPhase.full_moon(new(year, month, :full_moon, -0.5).time),
21
+ MoonPhase.last_quarter(new(year, month, :last_quarter, -0.25).time),
22
+ MoonPhase.new_moon(new(year, month, :new_moon, 0).time),
23
+ MoonPhase.first_quarter(new(year, month, :first_quarter, 0.25).time),
24
+ MoonPhase.full_moon(new(year, month, :full_moon, 0.5).time),
25
+ MoonPhase.last_quarter(new(year, month, :last_quarter, 0.75).time),
26
+ MoonPhase.new_moon(new(year, month, :new_moon, 1).time)
27
+ ].select { _1.time.month == month }
28
+ end
29
+
30
+ # @param year [Integer] Requested year
31
+ # @param month [Integer] Requested month
32
+ # @param phase [Symbol] Moon phase
33
+ # @param phase_increment [Float] Phase increment
34
+ def initialize(year, month, phase, phase_increment)
35
+ @year = year
36
+ @month = month
37
+ @phase = phase
38
+ @phase_increment = phase_increment
39
+ end
40
+
41
+ # @return [Time] Time of the Moon phase
42
+ def time
43
+ correction = moon_phases_periodic_terms
44
+ .public_send(:"#{@phase}_correction")
45
+ terrestrial_time = Epoch.to_utc(
46
+ julian_ephemeris_day +
47
+ correction +
48
+ moon_phases_periodic_terms.additional_corrections
49
+ )
50
+ delta = Util::Time.terrestrial_universal_time_delta(terrestrial_time)
51
+ (terrestrial_time - delta).round
52
+ end
53
+
54
+ private
55
+
56
+ def portion_of_year
57
+ days_in_year = Date.new(@year, 12, 31) - Date.new(@year, 1, 1)
58
+ mid_month = Date.new(@year, @month, 15)
59
+ mid_month.yday / days_in_year.to_f
60
+ end
61
+
62
+ def approximate_time
63
+ ((@year + portion_of_year - BASE_YEAR) * 12.3685).floor + @phase_increment
64
+ end
65
+
66
+ def julian_centuries
67
+ approximate_time / 1236.85
68
+ end
69
+
70
+ def julian_ephemeris_day
71
+ 2451550.09766 +
72
+ 29.530588861 * approximate_time +
73
+ 0.00015437 * julian_centuries**2 -
74
+ 0.000000150 * julian_centuries**3 +
75
+ 0.00000000073 * julian_centuries**4
76
+ end
77
+
78
+ def eccentricity_correction
79
+ 1 -
80
+ 0.002516 * julian_centuries -
81
+ 0.0000074 * julian_centuries**2
82
+ end
83
+
84
+ def sun_mean_anomaly
85
+ Angle.from_degrees(
86
+ (
87
+ 2.5534 +
88
+ 29.10535670 * approximate_time -
89
+ 0.0000014 * julian_centuries**2 -
90
+ 0.00000011 * julian_centuries**3
91
+ ) % 360
92
+ )
93
+ end
94
+
95
+ def moon_mean_anomaly
96
+ Angle.from_degrees(
97
+ (
98
+ 201.5643 +
99
+ 385.81693528 * approximate_time +
100
+ 0.0107582 * julian_centuries**2 +
101
+ 0.00001238 * julian_centuries**3 -
102
+ 0.000000058 * julian_centuries**4
103
+ ) % 360
104
+ )
105
+ end
106
+
107
+ def moon_argument_of_latitude
108
+ Angle.from_degrees(
109
+ (
110
+ 160.7108 +
111
+ 390.67050284 * approximate_time -
112
+ 0.0016118 * julian_centuries**2 -
113
+ 0.00000227 * julian_centuries**3 +
114
+ 0.000000011 * julian_centuries**4
115
+ ) % 360
116
+ )
117
+ end
118
+
119
+ def longitude_of_the_ascending_node
120
+ Angle.from_degrees(
121
+ (
122
+ 124.7746 -
123
+ 1.56375588 * approximate_time +
124
+ 0.0020672 * julian_centuries**2 +
125
+ 0.00000215 * julian_centuries**3
126
+ ) % 360
127
+ )
128
+ end
129
+
130
+ def moon_phases_periodic_terms
131
+ MoonPhasesPeriodicTerms.new(
132
+ julian_centuries: julian_centuries,
133
+ time: approximate_time,
134
+ eccentricity_correction: eccentricity_correction,
135
+ moon_mean_anomaly: moon_mean_anomaly,
136
+ sun_mean_anomaly: sun_mean_anomaly,
137
+ moon_argument_of_latitude: moon_argument_of_latitude,
138
+ longitude_of_the_ascending_node: longitude_of_the_ascending_node
139
+ )
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,259 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Events
5
+ class ObservationEvents
6
+ STANDARD_ALTITUDE = Angle.from_dms(0, -34, 0)
7
+ RISING_SETTING_HOUR_ANGLE_RATIO_RANGE = (-1..1)
8
+ EARTH_SIDEREAL_ROTATION_RATE = 360.98564736629
9
+ ITERATION_PRECISION = 0.0001
10
+
11
+ attr_reader :rising_time,
12
+ :rising_azimuth,
13
+ :transit_time,
14
+ :transit_altitude,
15
+ :setting_time,
16
+ :setting_azimuth
17
+
18
+ # Source:
19
+ # Title: Astronomical Algorithms
20
+ # Author: Jean Meeus
21
+ # Edition: 2nd edition
22
+ # Chapter: 15 - Rising, Transit, and Setting
23
+
24
+ # @param observer [Astronoby::Observer] Observer
25
+ # @param date [Date] Date of the event
26
+ # @param coordinates_of_the_previous_day [Astronoby::Coordinates::Equatorial]
27
+ # Coordinates of the body of the previous day
28
+ # @param coordinates_of_the_day [Astronoby::Coordinates::Equatorial]
29
+ # Coordinates of the body of the day
30
+ # @param coordinates_of_the_next_day [Astronoby::Coordinates::Equatorial]
31
+ # Coordinates of the body of the next day
32
+ # @param additional_altitude [Astronoby::Angle] Additional altitude to the
33
+ # standard altitude adjustment
34
+ def initialize(
35
+ observer:,
36
+ date:,
37
+ coordinates_of_the_previous_day:,
38
+ coordinates_of_the_day:,
39
+ coordinates_of_the_next_day:,
40
+ additional_altitude: Angle.zero
41
+ )
42
+ @observer = observer
43
+ @date = date
44
+ @coordinates_of_the_previous_day = coordinates_of_the_previous_day
45
+ @coordinates_of_the_day = coordinates_of_the_day
46
+ @coordinates_of_the_next_day = coordinates_of_the_next_day
47
+ @standard_altitude = STANDARD_ALTITUDE
48
+ @additional_altitude = additional_altitude
49
+ compute
50
+ end
51
+
52
+ private
53
+
54
+ def compute
55
+ @initial_transit = initial_transit
56
+ @transit_time = Util::Time.decimal_hour_to_time(@date, @initial_transit)
57
+ @transit_altitude = local_horizontal_altitude_transit
58
+
59
+ return if h0.nil?
60
+
61
+ initial_rising = rationalize_decimal_time(
62
+ @initial_transit - h0.degrees / Constants::DEGREES_PER_CIRCLE
63
+ )
64
+
65
+ initial_setting = rationalize_decimal_time(
66
+ @initial_transit + h0.degrees / Constants::DEGREES_PER_CIRCLE
67
+ )
68
+
69
+ @final_rising, @final_transit, @final_setting =
70
+ iterate(initial_rising, @initial_transit, initial_setting)
71
+
72
+ rationalized_corrected_rising = rationalize_decimal_hours(
73
+ Constants::HOURS_PER_DAY * @final_rising
74
+ )
75
+ rationalized_corrected_transit = rationalize_decimal_hours(
76
+ Constants::HOURS_PER_DAY * @final_transit
77
+ )
78
+ rationalized_corrected_setting = rationalize_decimal_hours(
79
+ Constants::HOURS_PER_DAY * @final_setting
80
+ )
81
+
82
+ @rising_time = Util::Time.decimal_hour_to_time(@date, rationalized_corrected_rising)
83
+ @rising_azimuth = local_horizontal_azimuth_rising
84
+ @transit_time = Util::Time.decimal_hour_to_time(@date, rationalized_corrected_transit)
85
+ @transit_altitude = local_horizontal_altitude_transit
86
+ @setting_time = Util::Time.decimal_hour_to_time(@date, rationalized_corrected_setting)
87
+ @setting_azimuth = local_horizontal_azimuth_setting
88
+ end
89
+
90
+ def iterate(initial_rising, initial_transit, initial_setting)
91
+ delta = 1
92
+ corrected_rising = initial_rising
93
+ corrected_transit = initial_transit
94
+ corrected_setting = initial_setting
95
+ until delta < ITERATION_PRECISION
96
+ iterate = RiseTransitSetIteration.new(
97
+ observer: @observer,
98
+ date: @date,
99
+ coordinates_of_the_next_day: @coordinates_of_the_next_day,
100
+ coordinates_of_the_day: @coordinates_of_the_day,
101
+ coordinates_of_the_previous_day: @coordinates_of_the_previous_day,
102
+ shift: shift,
103
+ initial_rising: corrected_rising,
104
+ initial_transit: corrected_transit,
105
+ initial_setting: corrected_setting
106
+ ).iterate
107
+ delta = iterate.sum
108
+ corrected_rising = rationalize_decimal_time corrected_rising + iterate[0]
109
+ corrected_transit = rationalize_decimal_time corrected_transit + iterate[1]
110
+ corrected_setting = rationalize_decimal_time corrected_setting + iterate[2]
111
+ end
112
+ [corrected_rising, corrected_transit, corrected_setting]
113
+ end
114
+
115
+ def observer_longitude
116
+ # Longitude must be treated positively westwards from the meridian of
117
+ # Greenwich, and negatively to the east
118
+ -@observer.longitude
119
+ end
120
+
121
+ def initial_transit
122
+ rationalize_decimal_time(
123
+ (
124
+ @coordinates_of_the_day.right_ascension.degrees +
125
+ observer_longitude.degrees -
126
+ apparent_gst_at_midnight.degrees
127
+ ) / Constants::DEGREES_PER_CIRCLE
128
+ )
129
+ end
130
+
131
+ def h0
132
+ @h0 ||= begin
133
+ term1 = shift.sin -
134
+ @observer.latitude.sin * @coordinates_of_the_day.declination.sin
135
+ term2 = @observer.latitude.cos * @coordinates_of_the_day.declination.cos
136
+ ratio = term1 / term2
137
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(ratio)
138
+
139
+ Angle.acos(ratio)
140
+ end
141
+ end
142
+
143
+ def apparent_gst_at_midnight
144
+ Angle.from_hours(
145
+ GreenwichSiderealTime.from_utc(
146
+ Time.utc(@date.year, @date.month, @date.day)
147
+ ).time
148
+ )
149
+ end
150
+
151
+ def gst_transit
152
+ Angle.from_degrees(
153
+ apparent_gst_at_midnight.degrees +
154
+ EARTH_SIDEREAL_ROTATION_RATE * (@final_transit || @initial_transit)
155
+ )
156
+ end
157
+
158
+ def leap_day_portion
159
+ leap_seconds = Util::Time.terrestrial_universal_time_delta(@date)
160
+ leap_seconds / Constants::SECONDS_PER_DAY
161
+ end
162
+
163
+ def local_hour_angle_transit
164
+ gst_transit - observer_longitude - right_ascension_transit
165
+ end
166
+
167
+ def local_horizontal_azimuth_rising
168
+ term1 = declination_rising.sin + (-shift).sin * @observer.latitude.cos
169
+ term2 = (-shift).cos * @observer.latitude.cos
170
+ angle = term1 / term2
171
+ Angle.acos(angle)
172
+ end
173
+
174
+ def local_horizontal_altitude_transit
175
+ Angle.asin(
176
+ @observer.latitude.sin * declination_transit.sin +
177
+ @observer.latitude.cos * declination_transit.cos * local_hour_angle_transit.cos
178
+ )
179
+ end
180
+
181
+ def local_horizontal_azimuth_setting
182
+ term1 = declination_setting.sin + (-shift).sin * @observer.latitude.cos
183
+ term2 = (-shift).cos * @observer.latitude.cos
184
+ angle = term1 / term2
185
+ Angle.from_degrees(
186
+ Constants::DEGREES_PER_CIRCLE - Angle.acos(angle).degrees
187
+ )
188
+ end
189
+
190
+ def rationalize_decimal_time(decimal_time)
191
+ decimal_time += 1 if decimal_time.negative?
192
+ decimal_time -= 1 if decimal_time > 1
193
+ decimal_time
194
+ end
195
+
196
+ def rationalize_decimal_hours(decimal_hours)
197
+ decimal_hours += Constants::HOURS_PER_DAY if decimal_hours.negative?
198
+ decimal_hours -= Constants::HOURS_PER_DAY if decimal_hours > Constants::HOURS_PER_DAY
199
+ decimal_hours
200
+ end
201
+
202
+ def right_ascension_transit
203
+ Angle.from_degrees(
204
+ Util::Maths.interpolate(
205
+ [
206
+ @coordinates_of_the_previous_day.right_ascension.degrees,
207
+ @coordinates_of_the_day.right_ascension.degrees,
208
+ @coordinates_of_the_next_day.right_ascension.degrees
209
+ ],
210
+ (@final_transit || @initial_transit) + leap_day_portion
211
+ )
212
+ )
213
+ end
214
+
215
+ def declination_rising
216
+ Angle.from_degrees(
217
+ Util::Maths.interpolate(
218
+ [
219
+ @coordinates_of_the_previous_day.declination.degrees,
220
+ @coordinates_of_the_day.declination.degrees,
221
+ @coordinates_of_the_next_day.declination.degrees
222
+ ],
223
+ @final_rising + leap_day_portion
224
+ )
225
+ )
226
+ end
227
+
228
+ def declination_transit
229
+ Angle.from_degrees(
230
+ Util::Maths.interpolate(
231
+ [
232
+ @coordinates_of_the_previous_day.declination.degrees,
233
+ @coordinates_of_the_day.declination.degrees,
234
+ @coordinates_of_the_next_day.declination.degrees
235
+ ],
236
+ (@final_transit || @initial_transit) + leap_day_portion
237
+ )
238
+ )
239
+ end
240
+
241
+ def declination_setting
242
+ Angle.from_degrees(
243
+ Util::Maths.interpolate(
244
+ [
245
+ @coordinates_of_the_previous_day.declination.degrees,
246
+ @coordinates_of_the_day.declination.degrees,
247
+ @coordinates_of_the_next_day.declination.degrees
248
+ ],
249
+ @final_setting + leap_day_portion
250
+ )
251
+ )
252
+ end
253
+
254
+ def shift
255
+ @standard_altitude - @additional_altitude
256
+ end
257
+ end
258
+ end
259
+ end