lunation 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +37 -0
- data/CHANGELOG.md +3 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +114 -0
- data/Rakefile +12 -0
- data/config/jpl_horizons_lunar_ephemeris_1950_2050.csv +3654 -0
- data/config/periodic_terms_earth_nutation.yml +567 -0
- data/config/periodic_terms_earth_position_b0.yml +5 -0
- data/config/periodic_terms_earth_position_b1.yml +2 -0
- data/config/periodic_terms_earth_position_l0.yml +64 -0
- data/config/periodic_terms_earth_position_l1.yml +34 -0
- data/config/periodic_terms_earth_position_l2.yml +20 -0
- data/config/periodic_terms_earth_position_l3.yml +7 -0
- data/config/periodic_terms_earth_position_l4.yml +3 -0
- data/config/periodic_terms_earth_position_l5.yml +1 -0
- data/config/periodic_terms_earth_position_r0.yml +40 -0
- data/config/periodic_terms_earth_position_r1.yml +10 -0
- data/config/periodic_terms_earth_position_r2.yml +6 -0
- data/config/periodic_terms_earth_position_r3.yml +2 -0
- data/config/periodic_terms_earth_position_r4.yml +1 -0
- data/config/periodic_terms_moon_latitude.yml +300 -0
- data/config/periodic_terms_moon_longitude_distance.yml +360 -0
- data/lib/lunation/calculation/angle.rb +174 -0
- data/lib/lunation/calculation/earth_position_vsop87.rb +80 -0
- data/lib/lunation/calculation/horner.rb +26 -0
- data/lib/lunation/calculation/moon_illuminated_fraction.rb +51 -0
- data/lib/lunation/calculation/moon_position.rb +227 -0
- data/lib/lunation/calculation/nutation_and_obliquity.rb +121 -0
- data/lib/lunation/calculation/sun_position.rb +124 -0
- data/lib/lunation/calculation/timekeeping.rb +99 -0
- data/lib/lunation/calculation.rb +170 -0
- data/lib/lunation/version.rb +5 -0
- data/lib/lunation.rb +11 -0
- data/sig/lunation.rbs +4 -0
- metadata +82 -0
@@ -0,0 +1,174 @@
|
|
1
|
+
module Lunation
|
2
|
+
class Calculation
|
3
|
+
class Angle
|
4
|
+
attr_reader :radians, :decimal_degrees
|
5
|
+
|
6
|
+
def initialize(
|
7
|
+
radians: nil,
|
8
|
+
decimal_degrees: nil,
|
9
|
+
decimal_arcseconds: nil,
|
10
|
+
arcminutes: nil,
|
11
|
+
degrees: nil,
|
12
|
+
hours: nil,
|
13
|
+
minutes: nil,
|
14
|
+
decimal_seconds: nil
|
15
|
+
)
|
16
|
+
@radians = radians
|
17
|
+
@decimal_degrees = decimal_degrees
|
18
|
+
@decimal_arcseconds = decimal_arcseconds
|
19
|
+
@arcminutes = arcminutes
|
20
|
+
@degrees = degrees
|
21
|
+
@hours = hours
|
22
|
+
@minutes = minutes
|
23
|
+
@decimal_seconds = decimal_seconds
|
24
|
+
end
|
25
|
+
|
26
|
+
def decimal_arcminutes
|
27
|
+
@decimal_arcminutes ||= (decimal_degrees - degrees) * 60
|
28
|
+
end
|
29
|
+
|
30
|
+
def decimal_arcseconds
|
31
|
+
@decimal_arcseconds ||= ((decimal_arcminutes - arcminutes) * 60).round(6)
|
32
|
+
end
|
33
|
+
|
34
|
+
def degrees
|
35
|
+
@degrees ||= decimal_degrees.to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
def arcminutes
|
39
|
+
@arcminutes ||= decimal_arcminutes.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def decimal_hours
|
43
|
+
@decimal_hours ||= decimal_degrees / 15.0
|
44
|
+
end
|
45
|
+
|
46
|
+
def decimal_minutes
|
47
|
+
@decimal_minutes ||= (decimal_hours - hours) * 60
|
48
|
+
end
|
49
|
+
|
50
|
+
def decimal_seconds
|
51
|
+
@decimal_seconds ||= ((decimal_minutes - minutes) * 60).round(6)
|
52
|
+
end
|
53
|
+
|
54
|
+
def hours
|
55
|
+
@hours ||= decimal_hours.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
def minutes
|
59
|
+
@minutes ||= decimal_minutes.to_i
|
60
|
+
end
|
61
|
+
|
62
|
+
def +(other)
|
63
|
+
self.class.from_decimal_degrees(
|
64
|
+
decimal_degrees + other.decimal_degrees,
|
65
|
+
normalize: false
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def -(other)
|
70
|
+
self.class.from_decimal_degrees(
|
71
|
+
decimal_degrees - other.decimal_degrees,
|
72
|
+
normalize: false
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
class << self
|
77
|
+
def from_radians(radians, normalize: true)
|
78
|
+
new(
|
79
|
+
radians: radians,
|
80
|
+
decimal_degrees: radians_to_decimal_degrees(radians, normalize: normalize)
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def from_decimal_degrees(decimal_degrees, normalize: true)
|
85
|
+
new(
|
86
|
+
decimal_degrees: (normalize ? decimal_degrees % 360 : decimal_degrees).round(9),
|
87
|
+
radians: decimal_degrees_to_radians(decimal_degrees, normalize: normalize)
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def from_degrees(degrees:, arcminutes:, decimal_arcseconds:, normalize: true)
|
92
|
+
decimal_degrees = degrees_arcminutes_decimal_arcseconds_to_decimal_degrees(
|
93
|
+
degrees,
|
94
|
+
arcminutes,
|
95
|
+
decimal_arcseconds,
|
96
|
+
normalize: normalize
|
97
|
+
)
|
98
|
+
new(
|
99
|
+
decimal_degrees: decimal_degrees,
|
100
|
+
degrees: degrees,
|
101
|
+
arcminutes: arcminutes,
|
102
|
+
decimal_arcseconds: decimal_arcseconds
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def from_hours_minutes_decimal_seconds(
|
107
|
+
hours:,
|
108
|
+
minutes:,
|
109
|
+
decimal_seconds:,
|
110
|
+
normalize: true
|
111
|
+
)
|
112
|
+
decimal_degrees = hours_minutes_decimal_seconds_to_decimal_degrees(
|
113
|
+
hours,
|
114
|
+
minutes,
|
115
|
+
decimal_seconds,
|
116
|
+
normalize: normalize
|
117
|
+
)
|
118
|
+
new(
|
119
|
+
decimal_degrees: decimal_degrees,
|
120
|
+
hours: hours,
|
121
|
+
minutes: minutes,
|
122
|
+
decimal_seconds: decimal_seconds
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def from_decimal_arcseconds(decimal_arcseconds, normalize: true)
|
127
|
+
decimal_degrees = decimal_arcseconds_to_decimal_degrees(
|
128
|
+
decimal_arcseconds,
|
129
|
+
normalize: normalize
|
130
|
+
)
|
131
|
+
new(decimal_degrees: decimal_degrees, decimal_arcseconds: decimal_arcseconds)
|
132
|
+
end
|
133
|
+
|
134
|
+
private def radians_to_decimal_degrees(radians, normalize: true)
|
135
|
+
result = radians / Math::PI * 180
|
136
|
+
(normalize ? result % 360 : result).round(9)
|
137
|
+
end
|
138
|
+
|
139
|
+
private def decimal_degrees_to_radians(decimal_degrees, normalize: true)
|
140
|
+
result = (normalize ? decimal_degrees % 360 : decimal_degrees) * Math::PI / 180.0
|
141
|
+
result.round(9)
|
142
|
+
end
|
143
|
+
|
144
|
+
private def degrees_arcminutes_decimal_arcseconds_to_decimal_degrees(
|
145
|
+
degrees,
|
146
|
+
arcminutes,
|
147
|
+
decimal_arcseconds,
|
148
|
+
normalize: true
|
149
|
+
)
|
150
|
+
result = degrees + arcminutes / 60.0 + decimal_arcseconds / 3600.0
|
151
|
+
(normalize ? result % 360 : result).round(9)
|
152
|
+
end
|
153
|
+
|
154
|
+
private def hours_minutes_decimal_seconds_to_decimal_degrees(
|
155
|
+
hours,
|
156
|
+
minutes,
|
157
|
+
decimal_seconds,
|
158
|
+
normalize: true
|
159
|
+
)
|
160
|
+
result = hours * 15.0 + minutes / 4.0 + decimal_seconds / 240.0
|
161
|
+
(normalize ? result % 360 : result).round(9)
|
162
|
+
end
|
163
|
+
|
164
|
+
private def decimal_arcseconds_to_decimal_degrees(
|
165
|
+
decimal_arcseconds,
|
166
|
+
normalize: true
|
167
|
+
)
|
168
|
+
result = decimal_arcseconds / 3600.0
|
169
|
+
(normalize ? result % 360 : result).round(9)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Lunation
|
2
|
+
class Calculation
|
3
|
+
module EarthPositionVSOP87
|
4
|
+
# rubocop:disable Layout/LineLength
|
5
|
+
PERIODIC_TERMS_B0 = YAML.load_file("config/periodic_terms_earth_position_b0.yml").freeze
|
6
|
+
PERIODIC_TERMS_B1 = YAML.load_file("config/periodic_terms_earth_position_b1.yml").freeze
|
7
|
+
PERIODIC_TERMS_L0 = YAML.load_file("config/periodic_terms_earth_position_l0.yml").freeze
|
8
|
+
PERIODIC_TERMS_L1 = YAML.load_file("config/periodic_terms_earth_position_l1.yml").freeze
|
9
|
+
PERIODIC_TERMS_L2 = YAML.load_file("config/periodic_terms_earth_position_l2.yml").freeze
|
10
|
+
PERIODIC_TERMS_L3 = YAML.load_file("config/periodic_terms_earth_position_l3.yml").freeze
|
11
|
+
PERIODIC_TERMS_L4 = YAML.load_file("config/periodic_terms_earth_position_l4.yml").freeze
|
12
|
+
PERIODIC_TERMS_L5 = YAML.load_file("config/periodic_terms_earth_position_l5.yml").freeze
|
13
|
+
PERIODIC_TERMS_R0 = YAML.load_file("config/periodic_terms_earth_position_r0.yml").freeze
|
14
|
+
PERIODIC_TERMS_R1 = YAML.load_file("config/periodic_terms_earth_position_r1.yml").freeze
|
15
|
+
PERIODIC_TERMS_R2 = YAML.load_file("config/periodic_terms_earth_position_r2.yml").freeze
|
16
|
+
PERIODIC_TERMS_R3 = YAML.load_file("config/periodic_terms_earth_position_r3.yml").freeze
|
17
|
+
PERIODIC_TERMS_R4 = YAML.load_file("config/periodic_terms_earth_position_r4.yml").freeze
|
18
|
+
# rubocop:enable Layout/LineLength
|
19
|
+
|
20
|
+
# (L) Ecliptical longitude of the earth (A.A. p. 219, 32.2)
|
21
|
+
# UNIT: Angle
|
22
|
+
def ecliptic_longitude_of_earth_using_vsop87
|
23
|
+
@ecliptic_longitude_of_earth_using_vsop87 ||= begin
|
24
|
+
result = Horner.compute(
|
25
|
+
time_millennia,
|
26
|
+
[
|
27
|
+
reduce_periodic_terms(PERIODIC_TERMS_L0),
|
28
|
+
reduce_periodic_terms(PERIODIC_TERMS_L1),
|
29
|
+
reduce_periodic_terms(PERIODIC_TERMS_L2),
|
30
|
+
reduce_periodic_terms(PERIODIC_TERMS_L3),
|
31
|
+
reduce_periodic_terms(PERIODIC_TERMS_L4),
|
32
|
+
reduce_periodic_terms(PERIODIC_TERMS_L5)
|
33
|
+
]
|
34
|
+
)
|
35
|
+
|
36
|
+
Angle.from_radians(result / 100_000_000.0)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# (B) Ecliptical latitude of the earth (A.A. p. 219, 32.2)
|
41
|
+
# UNIT: Angle
|
42
|
+
def ecliptic_latitude_of_earth_using_vsop87
|
43
|
+
@ecliptic_latitude_of_earth_using_vsop87 ||= begin
|
44
|
+
first_series = reduce_periodic_terms(PERIODIC_TERMS_B0)
|
45
|
+
second_series = reduce_periodic_terms(PERIODIC_TERMS_B1)
|
46
|
+
|
47
|
+
Angle.from_radians(
|
48
|
+
(first_series + second_series * time_millennia) / 100_000_000.0,
|
49
|
+
normalize: false
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# (R) Radius vector (distance to sun) of the earth (A.A. p. 219, 32.2)
|
55
|
+
# UNIT: Astronomical Units (AU)
|
56
|
+
def radius_vector_of_earth_using_vsop87
|
57
|
+
@radius_vector_of_earth_using_vsop87 ||= begin
|
58
|
+
result = Horner.compute(
|
59
|
+
time_millennia,
|
60
|
+
[
|
61
|
+
reduce_periodic_terms(PERIODIC_TERMS_R0),
|
62
|
+
reduce_periodic_terms(PERIODIC_TERMS_R1),
|
63
|
+
reduce_periodic_terms(PERIODIC_TERMS_R2),
|
64
|
+
reduce_periodic_terms(PERIODIC_TERMS_R3),
|
65
|
+
reduce_periodic_terms(PERIODIC_TERMS_R4)
|
66
|
+
]
|
67
|
+
)
|
68
|
+
|
69
|
+
(result / 100_000_000.0).round(9)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private def reduce_periodic_terms(periodic_terms)
|
74
|
+
periodic_terms.inject(0.0) do |sum, terms|
|
75
|
+
sum + terms[0] * Math.cos(terms[1] + terms[2] * time_millennia)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lunation
|
2
|
+
class Calculation
|
3
|
+
class Horner
|
4
|
+
def initialize(variable, constants)
|
5
|
+
@variable = variable
|
6
|
+
@constants = constants
|
7
|
+
end
|
8
|
+
|
9
|
+
def compute
|
10
|
+
@constants.reverse_each.each_with_index.inject(0.0) do |sum, (constant, index)|
|
11
|
+
if index.zero?
|
12
|
+
constant
|
13
|
+
else
|
14
|
+
constant + @variable * sum
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def compute(variable, constants)
|
21
|
+
new(variable, constants).compute
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Lunation
|
2
|
+
class Calculation
|
3
|
+
module MoonIlluminatedFraction
|
4
|
+
# (k) illuminated fraction of the moon (48.1, A.A. p. 345)
|
5
|
+
# UNIT: fraction (decimal)
|
6
|
+
def moon_illuminated_fraction
|
7
|
+
@moon_illuminated_fraction ||=
|
8
|
+
((1 + Math.cos(moon_phase_angle.radians)) / 2.0).round(4)
|
9
|
+
end
|
10
|
+
|
11
|
+
# (i) phase angle of the moon (48.3, A.A. p. 346)
|
12
|
+
# UNIT: Angle
|
13
|
+
def moon_phase_angle
|
14
|
+
@moon_phase_angle ||= begin
|
15
|
+
numerator = distance_between_earth_and_sun_in_kilometers * Math.sin(moon_elongation_from_sun.radians)
|
16
|
+
denominator = distance_between_earth_and_moon -
|
17
|
+
distance_between_earth_and_sun_in_kilometers * Math.cos(moon_elongation_from_sun.radians)
|
18
|
+
Angle.from_radians(Math.atan2(numerator, denominator))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# (ψ) geocentric elongation of the moon (48.2, A.A. p. 345)
|
23
|
+
# UNIT: Angle
|
24
|
+
def moon_elongation_from_sun
|
25
|
+
@moon_elongation_from_sun ||= begin
|
26
|
+
result = Math.sin(sun_declination.radians) *
|
27
|
+
Math.sin(moon_declination.radians) +
|
28
|
+
Math.cos(sun_declination.radians) *
|
29
|
+
Math.cos(moon_declination.radians) *
|
30
|
+
Math.cos((sun_right_ascension - moon_right_ascension).radians)
|
31
|
+
Angle.from_radians(Math.acos(result))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# (χ) Position angle of the moon's bright limb (48.5, A.A. p. 346)
|
36
|
+
# UNIT: Angle
|
37
|
+
def moon_position_angle_of_bright_limb
|
38
|
+
@moon_position_angle_of_bright_limb ||= begin
|
39
|
+
numerator = Math.cos(sun_declination.radians) *
|
40
|
+
Math.sin((sun_right_ascension - moon_right_ascension).radians)
|
41
|
+
denominator = Math.sin(sun_declination.radians) *
|
42
|
+
Math.cos(moon_declination.radians) -
|
43
|
+
Math.cos(sun_declination.radians) *
|
44
|
+
Math.sin(moon_declination.radians) *
|
45
|
+
Math.cos((sun_right_ascension - moon_right_ascension).radians)
|
46
|
+
Angle.from_radians(Math.atan2(numerator, denominator))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
require_relative "nutation_and_obliquity"
|
2
|
+
|
3
|
+
module Lunation
|
4
|
+
class Calculation
|
5
|
+
module MoonPosition
|
6
|
+
# rubocop:disable Layout/LineLength
|
7
|
+
LONGITUDE_AND_DISTANCE_OF_MOON_PERIODIC_TERMS = YAML.load_file("config/periodic_terms_moon_longitude_distance.yml").freeze
|
8
|
+
LATITUDE_OF_MOON_PERIODIC_TERMS = YAML.load_file("config/periodic_terms_moon_latitude.yml").freeze
|
9
|
+
MOON_MEAN_LONGITUDE_CONSTANTS = [218.3164477, 481_267.88123421, -0.0015786, 1 / 538_841.0, -1 / 65_194_000.0].freeze
|
10
|
+
MOON_MEAN_ELONGATION_CONSTANTS = [297.8501921, 445_267.1114034, -0.0018819, 1 / 545_868.0, -1 / 113_065_000.0].freeze
|
11
|
+
MOON_MEAN_ANOMALY_CONSTANTS = [134.9633964, 477_198.8675055, 0.0087414, 1 / 69_699.0, -1 / 14_712_000.0].freeze
|
12
|
+
MOON_ARGUMENT_OF_LATITUDE_CONSTANTS = [93.2720950, 483_202.0175233, -0.0036539, -1 / 3_526_000.0, 1 / 863_310_000.0].freeze
|
13
|
+
CORRECTION_ECCENTRICITY_OF_EARTH_CONSTANTS = [1, -0.002516, -0.0000074].freeze
|
14
|
+
FIXED_DISTANCE_BETWEEN_EARTH_AND_MOON = 385_000.56
|
15
|
+
RADIUS_OF_EARTH = 6378.14
|
16
|
+
# rubocop:enable Layout/LineLength
|
17
|
+
|
18
|
+
# (L') Moon mean_longitude (47.1, A.A. p. 338)
|
19
|
+
# UNIT: Angle
|
20
|
+
def moon_mean_longitude
|
21
|
+
@moon_mean_longitude ||=
|
22
|
+
Angle.from_decimal_degrees(Horner.compute(time, MOON_MEAN_LONGITUDE_CONSTANTS))
|
23
|
+
end
|
24
|
+
|
25
|
+
# (D) Moon mean_elongation (47.2, A.A. p. 338)
|
26
|
+
# UNIT: Angle
|
27
|
+
def moon_mean_elongation_from_sun_high_precision
|
28
|
+
@moon_mean_elongation_from_sun_high_precision ||=
|
29
|
+
Angle.from_decimal_degrees(Horner.compute(time, MOON_MEAN_ELONGATION_CONSTANTS))
|
30
|
+
end
|
31
|
+
|
32
|
+
# (M') Moon mean_anomaly (47.4, A.A. p. 338)
|
33
|
+
# UNIT: Angle
|
34
|
+
def moon_mean_anomaly_high_precision
|
35
|
+
@moon_mean_anomaly_high_precision ||=
|
36
|
+
Angle.from_decimal_degrees(Horner.compute(time, MOON_MEAN_ANOMALY_CONSTANTS))
|
37
|
+
end
|
38
|
+
|
39
|
+
# (F) Moon argument_of_latitude (47.5, A.A. p. 338)
|
40
|
+
# UNIT: Angle
|
41
|
+
def moon_argument_of_latitude_high_precision
|
42
|
+
@moon_argument_of_latitude_high_precision ||= begin
|
43
|
+
result = Horner.compute(time, MOON_ARGUMENT_OF_LATITUDE_CONSTANTS)
|
44
|
+
Angle.from_decimal_degrees(result)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# (A1) Venus correction (A.A. p. 338)
|
49
|
+
# UNIT: Angle
|
50
|
+
def correction_venus
|
51
|
+
@correction_venus ||= Angle.from_decimal_degrees(119.75 + 131.849 * time)
|
52
|
+
end
|
53
|
+
|
54
|
+
# (A2) Jupiter correction (A.A. p. 338)
|
55
|
+
# UNIT: Angle
|
56
|
+
def correction_jupiter
|
57
|
+
@correction_jupiter ||= Angle.from_decimal_degrees(53.09 + 4_792_64.29 * time)
|
58
|
+
end
|
59
|
+
|
60
|
+
# (A3) latitude correction (A.A. p. 338)
|
61
|
+
# UNIT: Angle
|
62
|
+
def correction_latitude
|
63
|
+
@correction_latitude ||= Angle.from_decimal_degrees(313.45 + 481_266.484 * time)
|
64
|
+
end
|
65
|
+
|
66
|
+
# (E) Earth eccentricity (47.6 A.A. p. 338)
|
67
|
+
def correction_eccentricity_of_earth
|
68
|
+
@correction_eccentricity_of_earth ||= begin
|
69
|
+
result = Horner.compute(time, CORRECTION_ECCENTRICITY_OF_EARTH_CONSTANTS)
|
70
|
+
result.round(6)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# (Σl) Moon longitude (A.A. p. 338)
|
75
|
+
# UNIT: decimal degrees
|
76
|
+
def moon_heliocentric_longitude
|
77
|
+
@moon_heliocentric_longitude ||= begin
|
78
|
+
result = LONGITUDE_AND_DISTANCE_OF_MOON_PERIODIC_TERMS.inject(0.0) do |acc, elem|
|
79
|
+
sine_argument = Angle.from_decimal_degrees(
|
80
|
+
elem["moon_mean_elongation"] * moon_mean_elongation_from_sun_high_precision.decimal_degrees +
|
81
|
+
elem["sun_mean_anomaly"] * sun_mean_anomaly2.decimal_degrees +
|
82
|
+
elem["moon_mean_anomaly"] * moon_mean_anomaly_high_precision.decimal_degrees +
|
83
|
+
elem["moon_argument_of_latitude"] * moon_argument_of_latitude_high_precision.decimal_degrees
|
84
|
+
)
|
85
|
+
|
86
|
+
if elem["sine_coefficient"].nil?
|
87
|
+
next acc
|
88
|
+
elsif [1, -1].include?(elem["sun_mean_anomaly"])
|
89
|
+
acc + elem["sine_coefficient"] * correction_eccentricity_of_earth * Math.sin(sine_argument.radians)
|
90
|
+
elsif [-2, 2].include?(elem["sun_mean_anomaly"])
|
91
|
+
acc + elem["sine_coefficient"] * correction_eccentricity_of_earth**2 * Math.sin(sine_argument.radians)
|
92
|
+
else
|
93
|
+
acc + elem["sine_coefficient"] * Math.sin(sine_argument.radians)
|
94
|
+
end
|
95
|
+
end + 3958 * Math.sin(correction_venus.radians) +
|
96
|
+
1962 * Math.sin((moon_mean_longitude - moon_argument_of_latitude_high_precision).radians) +
|
97
|
+
318 * Math.sin(correction_jupiter.radians)
|
98
|
+
result.round
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# (Σb) Moon latitude (A.A. p. 338)
|
103
|
+
# UNIT: decimal degrees
|
104
|
+
def moon_heliocentric_latitude
|
105
|
+
@moon_heliocentric_latitude ||= begin
|
106
|
+
result = LATITUDE_OF_MOON_PERIODIC_TERMS.inject(0.0) do |acc, elem|
|
107
|
+
sine_argument = Angle.from_decimal_degrees(
|
108
|
+
elem["moon_mean_elongation"] * moon_mean_elongation_from_sun_high_precision.decimal_degrees +
|
109
|
+
elem["sun_mean_anomaly"] * sun_mean_anomaly2.decimal_degrees +
|
110
|
+
elem["moon_mean_anomaly"] * moon_mean_anomaly_high_precision.decimal_degrees +
|
111
|
+
elem["moon_argument_of_latitude"] * moon_argument_of_latitude_high_precision.decimal_degrees
|
112
|
+
)
|
113
|
+
|
114
|
+
if elem["sine_coefficient"].nil?
|
115
|
+
next acc
|
116
|
+
elsif [1, -1].include?(elem["sun_mean_anomaly"])
|
117
|
+
acc + elem["sine_coefficient"] * correction_eccentricity_of_earth * Math.sin(sine_argument.radians)
|
118
|
+
elsif [-2, 2].include?(elem["sun_mean_anomaly"])
|
119
|
+
acc + elem["sine_coefficient"] * correction_eccentricity_of_earth**2 * Math.sin(sine_argument.radians)
|
120
|
+
else
|
121
|
+
acc + elem["sine_coefficient"] * Math.sin(sine_argument.radians)
|
122
|
+
end
|
123
|
+
end - 2235 * Math.sin(moon_mean_longitude.radians) +
|
124
|
+
382 * Math.sin(correction_latitude.radians) +
|
125
|
+
175 * Math.sin((correction_venus - moon_argument_of_latitude_high_precision).radians) +
|
126
|
+
175 * Math.sin((correction_venus + moon_argument_of_latitude_high_precision).radians) +
|
127
|
+
127 * Math.sin((moon_mean_longitude - moon_mean_anomaly_high_precision).radians) +
|
128
|
+
-115 * Math.sin((moon_mean_longitude + moon_mean_anomaly_high_precision).radians)
|
129
|
+
result.round
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# (Σr) Moon distance (A.A. p. 338)
|
134
|
+
# UNIT: 1000 km
|
135
|
+
def moon_heliocentric_distance
|
136
|
+
@moon_heliocentric_distance ||= begin
|
137
|
+
result = LONGITUDE_AND_DISTANCE_OF_MOON_PERIODIC_TERMS.inject(0.0) do |acc, elem|
|
138
|
+
cosine_argument = Angle.from_decimal_degrees(
|
139
|
+
elem["moon_mean_elongation"] * moon_mean_elongation_from_sun_high_precision.decimal_degrees +
|
140
|
+
elem["sun_mean_anomaly"] * sun_mean_anomaly2.decimal_degrees +
|
141
|
+
elem["moon_mean_anomaly"] * moon_mean_anomaly_high_precision.decimal_degrees +
|
142
|
+
elem["moon_argument_of_latitude"] * moon_argument_of_latitude_high_precision.decimal_degrees
|
143
|
+
)
|
144
|
+
|
145
|
+
if elem["cosine_coefficient"].nil?
|
146
|
+
next acc
|
147
|
+
elsif [1, -1].include?(elem["sun_mean_anomaly"])
|
148
|
+
acc + elem["cosine_coefficient"] * correction_eccentricity_of_earth * Math.cos(cosine_argument.radians)
|
149
|
+
elsif [-2, 2].include?(elem["sun_mean_anomaly"])
|
150
|
+
acc + elem["cosine_coefficient"] * correction_eccentricity_of_earth**2 * Math.cos(cosine_argument.radians)
|
151
|
+
else
|
152
|
+
acc + elem["cosine_coefficient"] * Math.cos(cosine_argument.radians)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
result.round
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# (λ) ecliptical longitude (A.A. p. 342)
|
160
|
+
# UNIT: Angle
|
161
|
+
def moon_ecliptic_longitude
|
162
|
+
@moon_ecliptic_longitude ||= Angle.from_decimal_degrees(
|
163
|
+
moon_mean_longitude.decimal_degrees + moon_heliocentric_longitude / 1_000_000.0
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
# (β) ecliptical latitude (A.A. p. 342)
|
168
|
+
# UNIT: Angle
|
169
|
+
def moon_ecliptic_latitude
|
170
|
+
@moon_ecliptic_latitude ||= Angle.from_decimal_degrees(
|
171
|
+
moon_heliocentric_latitude / 1_000_000.0,
|
172
|
+
normalize: false
|
173
|
+
)
|
174
|
+
end
|
175
|
+
|
176
|
+
# (Δ) Earth-moon distance (in kilometers) (A.A. p. 342)
|
177
|
+
# UNIT: kilometers (km)
|
178
|
+
def distance_between_earth_and_moon
|
179
|
+
@distance_between_earth_and_moon ||= begin
|
180
|
+
result = FIXED_DISTANCE_BETWEEN_EARTH_AND_MOON +
|
181
|
+
(moon_heliocentric_distance / 1_000.0)
|
182
|
+
result.round(1)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# (π) Moon equitorial horizontal parallax (A.A. p. 337)
|
187
|
+
# UNIT: Angle
|
188
|
+
def equatorial_horizontal_parallax
|
189
|
+
@equatorial_horizontal_parallax ||= Angle.from_radians(
|
190
|
+
Math.asin(RADIUS_OF_EARTH / distance_between_earth_and_moon)
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
# (apparent λ) Moon apparent longitude (A.A. p. 343)
|
195
|
+
# UNIT: Angle
|
196
|
+
def moon_apparent_ecliptic_longitude
|
197
|
+
@moon_apparent_ecliptic_longitude ||= moon_ecliptic_longitude + nutation_in_longitude
|
198
|
+
end
|
199
|
+
|
200
|
+
# (α) geocentric (apparent) right ascension of the moon (13.3 A.A. p. 93)
|
201
|
+
# UNIT: Angle
|
202
|
+
def moon_right_ascension
|
203
|
+
@moon_right_ascension ||= begin
|
204
|
+
numerator = Math.sin(moon_apparent_ecliptic_longitude.radians) *
|
205
|
+
Math.cos(obliquity_of_ecliptic.radians) -
|
206
|
+
Math.tan(moon_ecliptic_latitude.radians) *
|
207
|
+
Math.sin(obliquity_of_ecliptic.radians)
|
208
|
+
denominator = Math.cos(moon_apparent_ecliptic_longitude.radians)
|
209
|
+
Angle.from_radians(Math.atan2(numerator, denominator))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# (δ) geocentric (apparent) declination of the moon (13.4) A.A. p. 93
|
214
|
+
# UNIT: Angle
|
215
|
+
def moon_declination
|
216
|
+
@moon_declination ||= begin
|
217
|
+
result = Math.sin(moon_ecliptic_latitude.radians) *
|
218
|
+
Math.cos(obliquity_of_ecliptic.radians) +
|
219
|
+
Math.cos(moon_ecliptic_latitude.radians) *
|
220
|
+
Math.sin(obliquity_of_ecliptic.radians) *
|
221
|
+
Math.sin(moon_apparent_ecliptic_longitude.radians)
|
222
|
+
Angle.from_radians(Math.asin(result))
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Lunation
|
2
|
+
class Calculation
|
3
|
+
module NutationAndObliquity
|
4
|
+
# rubocop:disable Layout/LineLength
|
5
|
+
NUTATION_IN_OBLIQUITY_PERIODIC_TERMS = YAML.load_file("config/periodic_terms_earth_nutation.yml").freeze
|
6
|
+
ECLIPTIC_MEAN_OBLIQUITY_CONSTANTS = [21.448, -4680.93, -1.55, 1_999.25, -51.38, -249.67, -39.05, 7.12, 27.87, 5.79, 2.45].freeze
|
7
|
+
MOON_MEAN_ELONGATION_FROM_SUN_CONSTANTS = [297.85036, 445_267.111480, -0.0019142, 1 / 189_474.0].freeze
|
8
|
+
SUN_MEAN_ANOMALY_CONSTANTS = [357.52772, 35_999.050340, -0.0001603, -1 / 300_000.0].freeze
|
9
|
+
MOON_MEAN_ANOMALY_CONSTANTS = [134.96298, 477_198.867398, 0.0086972, 1 / 56_250.0].freeze
|
10
|
+
MOON_ARGUMENT_OF_LATITUDE_CONSTANTS = [93.27191, 483_202.017538, -0.0036825, 1 / 327_270.0].freeze
|
11
|
+
LONGITUDE_OF_THE_ASCENDING_NODE_CONSTANTS = [125.04452, -1934.136261, 0.0020708, 1 / 450_000.0].freeze
|
12
|
+
# rubocop:enable Layout/LineLength
|
13
|
+
|
14
|
+
# (D) Mean elongation of the moon from the sun (A.A. p. 144)
|
15
|
+
# UNIT: Angle
|
16
|
+
def moon_mean_elongation_from_sun
|
17
|
+
@moon_mean_elongation_from_sun ||= begin
|
18
|
+
result = Horner.compute(time, MOON_MEAN_ELONGATION_FROM_SUN_CONSTANTS)
|
19
|
+
Angle.from_decimal_degrees(result)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# (M) Sun mean_anomaly (A.A. p. 144)
|
24
|
+
# UNIT: Angle
|
25
|
+
def sun_mean_anomaly
|
26
|
+
@sun_mean_anomaly ||= begin
|
27
|
+
result = Horner.compute(time, SUN_MEAN_ANOMALY_CONSTANTS)
|
28
|
+
Angle.from_decimal_degrees(result)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# (M') Moon mean_anomaly (A.A. p. 149)
|
33
|
+
# UNIT: Angle
|
34
|
+
def moon_mean_anomaly
|
35
|
+
@moon_mean_anomaly ||= begin
|
36
|
+
result = Horner.compute(time, MOON_MEAN_ANOMALY_CONSTANTS)
|
37
|
+
Angle.from_decimal_degrees(result)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# (F) Moon argument_of_latitude (A.A. p. 144)
|
42
|
+
# UNIT: Angle
|
43
|
+
def moon_argument_of_latitude
|
44
|
+
@moon_argument_of_latitude ||= begin
|
45
|
+
result = Horner.compute(time, MOON_ARGUMENT_OF_LATITUDE_CONSTANTS)
|
46
|
+
Angle.from_decimal_degrees(result)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# (Ω) Longitude of the ascending node of the Moon's mean orbit on the ecliptic,
|
51
|
+
# measured from the mean equinox of the date (A.A. p. 144)
|
52
|
+
# UNIT: Angle
|
53
|
+
def longitude_of_ascending_node
|
54
|
+
@longitude_of_ascending_node ||= begin
|
55
|
+
result = Horner.compute(time, LONGITUDE_OF_THE_ASCENDING_NODE_CONSTANTS)
|
56
|
+
Angle.from_decimal_degrees(result)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# (Δψ) nutation in longitude (A.A. p. 144)
|
61
|
+
# UNIT: Angle
|
62
|
+
def nutation_in_longitude
|
63
|
+
@nutation_in_longitude ||= begin
|
64
|
+
result = NUTATION_IN_OBLIQUITY_PERIODIC_TERMS.inject(0.0) do |acc, elem|
|
65
|
+
argument = Angle.from_decimal_degrees(
|
66
|
+
elem["moon_mean_elongation"] * moon_mean_elongation_from_sun.decimal_degrees +
|
67
|
+
elem["sun_mean_anomaly"] * sun_mean_anomaly.decimal_degrees +
|
68
|
+
elem["moon_mean_anomaly"] * moon_mean_anomaly.decimal_degrees +
|
69
|
+
elem["moon_argument_of_latitude"] * moon_argument_of_latitude.decimal_degrees +
|
70
|
+
elem["longitude_of_ascending_node"] * longitude_of_ascending_node.decimal_degrees
|
71
|
+
)
|
72
|
+
coefficient = elem["sine_coefficient_first_term"] + elem["sine_coefficient_second_term"] * time
|
73
|
+
acc + coefficient * Math.sin(argument.radians)
|
74
|
+
end / 10_000.0
|
75
|
+
Angle.from_decimal_arcseconds(result)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# (Δε) nutation in obliquity (A.A. p. 144)
|
80
|
+
# UNIT: Angle
|
81
|
+
def nutation_in_obliquity
|
82
|
+
@nutation_in_obliquity ||= begin
|
83
|
+
result = NUTATION_IN_OBLIQUITY_PERIODIC_TERMS.inject(0.0) do |acc, elem|
|
84
|
+
argument = Angle.from_decimal_degrees(
|
85
|
+
elem["moon_mean_elongation"] * moon_mean_elongation_from_sun.decimal_degrees +
|
86
|
+
elem["sun_mean_anomaly"] * sun_mean_anomaly.decimal_degrees +
|
87
|
+
elem["moon_mean_anomaly"] * moon_mean_anomaly.decimal_degrees +
|
88
|
+
elem["moon_argument_of_latitude"] * moon_argument_of_latitude.decimal_degrees +
|
89
|
+
elem["longitude_of_ascending_node"] * longitude_of_ascending_node.decimal_degrees
|
90
|
+
)
|
91
|
+
coefficient = elem["cosine_coefficient_first_term"] + elem["cosine_coefficient_second_term"] * time
|
92
|
+
acc + coefficient * Math.cos(argument.radians)
|
93
|
+
end / 10_000.0
|
94
|
+
Angle.from_decimal_arcseconds(result)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# (ε0) mean obliquity of the ecliptic (22.3, A.A. p. 147)
|
99
|
+
# UNIT: Angle
|
100
|
+
def mean_obliquity_of_ecliptic
|
101
|
+
@mean_obliquity_of_ecliptic ||= begin
|
102
|
+
decimal_arcseconds = Horner.compute(
|
103
|
+
time_myriads,
|
104
|
+
ECLIPTIC_MEAN_OBLIQUITY_CONSTANTS
|
105
|
+
)
|
106
|
+
Angle.from_degrees(
|
107
|
+
degrees: 23.0,
|
108
|
+
arcminutes: 26.0,
|
109
|
+
decimal_arcseconds: decimal_arcseconds
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# (ε) true obliquity of the ecliptic (A.A. p. 147)
|
115
|
+
# UNIT: Angle
|
116
|
+
def obliquity_of_ecliptic
|
117
|
+
@obliquity_of_ecliptic ||= mean_obliquity_of_ecliptic + nutation_in_obliquity
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|