astronoby 0.3.0 → 0.5.0

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