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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -0
- data/Gemfile.lock +19 -17
- data/README.md +195 -25
- data/UPGRADING.md +72 -0
- data/lib/astronoby/aberration.rb +7 -5
- data/lib/astronoby/angle.rb +26 -34
- data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +143 -0
- data/lib/astronoby/astronomical_models/moon_phases_periodic_terms.rb +249 -0
- data/lib/astronoby/bodies/moon.rb +335 -0
- data/lib/astronoby/bodies/sun.rb +129 -132
- data/lib/astronoby/constants.rb +31 -0
- data/lib/astronoby/coordinates/ecliptic.rb +4 -4
- data/lib/astronoby/coordinates/equatorial.rb +7 -5
- data/lib/astronoby/coordinates/horizontal.rb +24 -12
- data/lib/astronoby/distance.rb +83 -0
- data/lib/astronoby/epoch.rb +0 -2
- data/lib/astronoby/equinox_solstice.rb +3 -2
- data/lib/astronoby/events/moon_phases.rb +143 -0
- data/lib/astronoby/events/observation_events.rb +259 -0
- data/lib/astronoby/events/rise_transit_set_iteration.rb +215 -0
- data/lib/astronoby/events/twilight_events.rb +121 -0
- data/lib/astronoby/geocentric_parallax.rb +36 -56
- data/lib/astronoby/mean_obliquity.rb +2 -2
- data/lib/astronoby/moon_phase.rb +43 -0
- data/lib/astronoby/nutation.rb +5 -3
- data/lib/astronoby/observer.rb +39 -18
- data/lib/astronoby/precession.rb +1 -1
- data/lib/astronoby/refraction.rb +8 -10
- data/lib/astronoby/time/greenwich_sidereal_time.rb +18 -29
- data/lib/astronoby/time/local_sidereal_time.rb +4 -4
- data/lib/astronoby/util/maths.rb +72 -0
- data/lib/astronoby/util/time.rb +88 -0
- data/lib/astronoby/util/trigonometry.rb +4 -4
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +12 -1
- metadata +15 -4
- 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:
|
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 +=
|
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:,
|
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 =
|
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
|
-
|
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, :
|
6
|
+
attr_reader :azimuth, :altitude, :observer
|
7
7
|
|
8
8
|
def initialize(
|
9
9
|
azimuth:,
|
10
10
|
altitude:,
|
11
|
-
|
12
|
-
longitude:
|
11
|
+
observer:
|
13
12
|
)
|
14
13
|
@azimuth = azimuth
|
15
14
|
@altitude = altitude
|
16
|
-
@
|
17
|
-
@longitude = longitude
|
15
|
+
@observer = observer
|
18
16
|
end
|
19
17
|
|
20
18
|
def to_equatorial(time:)
|
21
|
-
t0 = @altitude.sin *
|
22
|
-
@altitude.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 -
|
24
|
+
t1 = @altitude.sin - latitude.sin * declination.sin
|
27
25
|
|
28
26
|
hour_angle_degrees = Angle
|
29
|
-
.acos(t1 / (
|
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(
|
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:
|
39
|
+
.to_lst(longitude: longitude)
|
42
40
|
right_ascension_decimal = lst.time - hour_angle_hours
|
43
|
-
|
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
|
data/lib/astronoby/epoch.rb
CHANGED
@@ -101,7 +101,7 @@ module Astronoby
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def compute
|
104
|
-
t = (julian_day - Epoch::J2000) /
|
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
|
-
|
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
|