astronoby 0.9.0 → 0.10.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/.ruby-version +1 -1
- data/CHANGELOG.md +101 -0
- data/README.md +6 -1
- data/UPGRADING.md +84 -0
- data/docs/README.md +80 -15
- data/docs/angles.md +1 -0
- data/docs/configuration.md +20 -17
- data/docs/coordinates.md +72 -12
- data/docs/deep_sky_bodies.md +1 -1
- data/docs/ephem.md +5 -2
- data/docs/equinoxes_solstices_times.md +4 -3
- data/docs/glossary.md +97 -1
- data/docs/iers.md +40 -0
- data/docs/instant.md +20 -15
- data/docs/lunar_eclipses.md +93 -0
- data/docs/lunar_observation.md +87 -0
- data/docs/moon_phases.md +4 -1
- data/docs/observer.md +20 -6
- data/docs/planetary_phenomena.md +78 -0
- data/docs/reference_frames.md +192 -34
- data/docs/rise_transit_set_times.md +6 -4
- data/docs/solar_system_bodies.md +26 -4
- data/docs/twilight_times.md +25 -21
- data/lib/astronoby/angle.rb +63 -2
- data/lib/astronoby/angles/dms.rb +18 -1
- data/lib/astronoby/angles/hms.rb +14 -1
- data/lib/astronoby/angular_velocity.rb +21 -0
- data/lib/astronoby/bodies/deep_sky_object.rb +6 -1
- data/lib/astronoby/bodies/deep_sky_object_position.rb +32 -17
- data/lib/astronoby/bodies/earth.rb +7 -44
- data/lib/astronoby/bodies/jupiter.rb +10 -0
- data/lib/astronoby/bodies/mars.rb +10 -0
- data/lib/astronoby/bodies/mercury.rb +10 -0
- data/lib/astronoby/bodies/moon.rb +158 -32
- data/lib/astronoby/bodies/neptune.rb +10 -0
- data/lib/astronoby/bodies/saturn.rb +10 -0
- data/lib/astronoby/bodies/solar_system_body.rb +240 -61
- data/lib/astronoby/bodies/sun.rb +79 -4
- data/lib/astronoby/bodies/uranus.rb +10 -0
- data/lib/astronoby/bodies/venus.rb +10 -0
- data/lib/astronoby/body.rb +6 -0
- data/lib/astronoby/center.rb +84 -0
- data/lib/astronoby/constellation.rb +9 -1
- data/lib/astronoby/coordinates/ecliptic.rb +10 -1
- data/lib/astronoby/coordinates/equatorial.rb +64 -8
- data/lib/astronoby/coordinates/geodetic.rb +102 -0
- data/lib/astronoby/coordinates/horizontal.rb +13 -1
- data/lib/astronoby/distance.rb +35 -0
- data/lib/astronoby/duration.rb +116 -0
- data/lib/astronoby/earth_rotation.rb +70 -0
- data/lib/astronoby/equinox_solstice.rb +31 -8
- data/lib/astronoby/errors.rb +11 -0
- data/lib/astronoby/events/conjunction.rb +51 -0
- data/lib/astronoby/events/conjunction_opposition_calculator.rb +84 -0
- data/lib/astronoby/events/eclipse_phase.rb +27 -0
- data/lib/astronoby/events/extremum_calculator.rb +23 -176
- data/lib/astronoby/events/greatest_elongation.rb +58 -0
- data/lib/astronoby/events/greatest_elongation_calculator.rb +56 -0
- data/lib/astronoby/events/lunar_eclipse.rb +99 -0
- data/lib/astronoby/events/lunar_eclipse_calculator.rb +285 -0
- data/lib/astronoby/events/opposition.rb +19 -0
- data/lib/astronoby/events/rise_transit_set_event.rb +12 -1
- data/lib/astronoby/events/rise_transit_set_events.rb +12 -1
- data/lib/astronoby/events/twilight_event.rb +24 -6
- data/lib/astronoby/events/twilight_events.rb +26 -6
- data/lib/astronoby/extremum_finder.rb +148 -0
- data/lib/astronoby/instant.rb +10 -7
- data/lib/astronoby/libration.rb +25 -0
- data/lib/astronoby/mean_obliquity.rb +8 -0
- data/lib/astronoby/moon_orientation_ephemeris.rb +69 -0
- data/lib/astronoby/moon_physical_ephemeris.rb +263 -0
- data/lib/astronoby/nutation.rb +10 -20
- data/lib/astronoby/observer.rb +67 -49
- data/lib/astronoby/orientation.rb +107 -0
- data/lib/astronoby/position.rb +16 -0
- data/lib/astronoby/precession.rb +61 -60
- data/lib/astronoby/reference_frame.rb +73 -7
- data/lib/astronoby/reference_frames/apparent.rb +26 -7
- data/lib/astronoby/reference_frames/astrometric.rb +14 -1
- data/lib/astronoby/reference_frames/geometric.rb +7 -1
- data/lib/astronoby/reference_frames/mean_of_date.rb +13 -1
- data/lib/astronoby/reference_frames/teme.rb +153 -0
- data/lib/astronoby/reference_frames/topocentric.rb +30 -4
- data/lib/astronoby/refraction.rb +26 -5
- data/lib/astronoby/root_finder.rb +83 -0
- data/lib/astronoby/rotation.rb +49 -0
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +9 -0
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +42 -5
- data/lib/astronoby/time/greenwich_sidereal_time.rb +21 -0
- data/lib/astronoby/time/local_apparent_sidereal_time.rb +21 -0
- data/lib/astronoby/time/local_mean_sidereal_time.rb +21 -0
- data/lib/astronoby/time/local_sidereal_time.rb +24 -0
- data/lib/astronoby/time/sidereal_time.rb +23 -1
- data/lib/astronoby/true_obliquity.rb +4 -0
- data/lib/astronoby/util/maths.rb +8 -0
- data/lib/astronoby/util/time.rb +10 -485
- data/lib/astronoby/vector.rb +10 -0
- data/lib/astronoby/velocity.rb +39 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +22 -0
- metadata +45 -5
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class ExtremumFinder
|
|
5
|
+
PHI = (1 + Math.sqrt(5)) / 2
|
|
6
|
+
INVPHI = 1 / PHI
|
|
7
|
+
GOLDEN_SECTION_TOLERANCE = 1e-5
|
|
8
|
+
|
|
9
|
+
MIN_SAMPLES_PER_PERIOD = 20
|
|
10
|
+
DUPLICATE_THRESHOLD_DAYS = 0.5
|
|
11
|
+
BOUNDARY_BUFFER_DAYS = 0.01
|
|
12
|
+
|
|
13
|
+
# @param value_at [#call] callable mapping a Julian Date (Terrestrial Time)
|
|
14
|
+
# to a comparable value
|
|
15
|
+
# @param period [Float] the characteristic period of the quantity in days,
|
|
16
|
+
# used to scale the sampling density
|
|
17
|
+
# @param samples_per_period [Integer] number of samples per period
|
|
18
|
+
def initialize(value_at:, period:, samples_per_period: 60)
|
|
19
|
+
@value_at = value_at
|
|
20
|
+
@period = period
|
|
21
|
+
@samples_per_period = samples_per_period
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param start_jd [Float] start time in Julian Date (Terrestrial Time)
|
|
25
|
+
# @param end_jd [Float] end time in Julian Date (Terrestrial Time)
|
|
26
|
+
# @param type [Symbol] +:maximum+ or +:minimum+
|
|
27
|
+
# @return [Array<Hash>] extrema as +{jd: Float, value: Comparable}+, sorted
|
|
28
|
+
# by time
|
|
29
|
+
def extrema(start_jd, end_jd, type: :maximum)
|
|
30
|
+
candidates = find_candidates(start_jd, end_jd, type)
|
|
31
|
+
refined = candidates.map { |candidate| refine(candidate, type) }.compact
|
|
32
|
+
refined = remove_duplicates(refined)
|
|
33
|
+
filter_boundary_artifacts(refined, start_jd, end_jd)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def find_candidates(start_jd, end_jd, type)
|
|
39
|
+
samples = collect_samples(start_jd, end_jd)
|
|
40
|
+
find_local_extrema(samples, type)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def collect_samples(start_jd, end_jd)
|
|
44
|
+
duration = end_jd - start_jd
|
|
45
|
+
sample_count = sample_count_for(duration)
|
|
46
|
+
step = duration / sample_count
|
|
47
|
+
|
|
48
|
+
(0..sample_count).map do |i|
|
|
49
|
+
jd = start_jd + (i * step)
|
|
50
|
+
{jd: jd, value: @value_at.call(jd)}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def sample_count_for(duration)
|
|
55
|
+
periods_in_range = duration / @period
|
|
56
|
+
base_samples = (periods_in_range * @samples_per_period).to_i
|
|
57
|
+
[base_samples, MIN_SAMPLES_PER_PERIOD].max
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def find_local_extrema(samples, type)
|
|
61
|
+
candidates = []
|
|
62
|
+
|
|
63
|
+
(1...samples.length - 1).each do |i|
|
|
64
|
+
next unless local_extremum?(samples, i, type)
|
|
65
|
+
|
|
66
|
+
candidates << {
|
|
67
|
+
start_jd: samples[i - 1][:jd],
|
|
68
|
+
end_jd: samples[i + 1][:jd]
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
candidates
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def local_extremum?(samples, index, type)
|
|
76
|
+
current = samples[index][:value]
|
|
77
|
+
previous = samples[index - 1][:value]
|
|
78
|
+
following = samples[index + 1][:value]
|
|
79
|
+
|
|
80
|
+
if type == :maximum
|
|
81
|
+
current > previous && current > following
|
|
82
|
+
else
|
|
83
|
+
current < previous && current < following
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def refine(candidate, type)
|
|
88
|
+
golden_section_search(candidate[:start_jd], candidate[:end_jd], type)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def golden_section_search(a, b, type)
|
|
92
|
+
return nil if b <= a
|
|
93
|
+
|
|
94
|
+
tol = GOLDEN_SECTION_TOLERANCE * (b - a).abs
|
|
95
|
+
|
|
96
|
+
x1 = a + (1 - INVPHI) * (b - a)
|
|
97
|
+
x2 = a + INVPHI * (b - a)
|
|
98
|
+
f1 = @value_at.call(x1)
|
|
99
|
+
f2 = @value_at.call(x2)
|
|
100
|
+
|
|
101
|
+
while (b - a).abs > tol
|
|
102
|
+
keep_left = (type == :maximum) ? (f1 > f2) : (f1 < f2)
|
|
103
|
+
|
|
104
|
+
if keep_left
|
|
105
|
+
b = x2
|
|
106
|
+
x2 = x1
|
|
107
|
+
f2 = f1
|
|
108
|
+
x1 = a + (1 - INVPHI) * (b - a)
|
|
109
|
+
f1 = @value_at.call(x1)
|
|
110
|
+
else
|
|
111
|
+
a = x1
|
|
112
|
+
x1 = x2
|
|
113
|
+
f1 = f2
|
|
114
|
+
x2 = a + INVPHI * (b - a)
|
|
115
|
+
f2 = @value_at.call(x2)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
mid = (a + b) / 2
|
|
120
|
+
{jd: mid, value: @value_at.call(mid)}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def remove_duplicates(extrema)
|
|
124
|
+
return extrema if extrema.length <= 1
|
|
125
|
+
|
|
126
|
+
cleaned = [extrema.first]
|
|
127
|
+
|
|
128
|
+
extrema.each_with_index do |current, i|
|
|
129
|
+
next if i == 0
|
|
130
|
+
|
|
131
|
+
is_duplicate = cleaned.any? do |existing|
|
|
132
|
+
(current[:jd] - existing[:jd]).abs < DUPLICATE_THRESHOLD_DAYS
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
cleaned << current unless is_duplicate
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
cleaned
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def filter_boundary_artifacts(extrema, start_jd, end_jd)
|
|
142
|
+
extrema.reject do |extreme|
|
|
143
|
+
(extreme[:jd] - start_jd).abs < BOUNDARY_BUFFER_DAYS ||
|
|
144
|
+
(extreme[:jd] - end_jd).abs < BOUNDARY_BUFFER_DAYS
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
data/lib/astronoby/instant.rb
CHANGED
|
@@ -62,6 +62,7 @@ module Astronoby
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# @return [Numeric] the Terrestrial Time as a Julian Date
|
|
65
66
|
attr_reader :terrestrial_time
|
|
66
67
|
alias_method :tt, :terrestrial_time
|
|
67
68
|
alias_method :julian_date, :terrestrial_time
|
|
@@ -77,6 +78,7 @@ module Astronoby
|
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
@terrestrial_time = terrestrial_time
|
|
81
|
+
@memo = {}
|
|
80
82
|
freeze
|
|
81
83
|
end
|
|
82
84
|
|
|
@@ -92,7 +94,7 @@ module Astronoby
|
|
|
92
94
|
#
|
|
93
95
|
# @return [DateTime] the UTC time as DateTime
|
|
94
96
|
def to_datetime
|
|
95
|
-
DateTime.jd(
|
|
97
|
+
@memo[:to_datetime] ||= DateTime.jd(
|
|
96
98
|
@terrestrial_time -
|
|
97
99
|
Rational(delta_t, Constants::SECONDS_PER_DAY) +
|
|
98
100
|
DATETIME_JD_EPOCH_ADJUSTMENT
|
|
@@ -110,7 +112,7 @@ module Astronoby
|
|
|
110
112
|
#
|
|
111
113
|
# @return [Time] the UTC time
|
|
112
114
|
def to_time
|
|
113
|
-
to_datetime.to_time.utc
|
|
115
|
+
@memo[:to_time] ||= to_datetime.to_time.utc
|
|
114
116
|
end
|
|
115
117
|
|
|
116
118
|
# Get the ΔT (Delta T) value for this instant
|
|
@@ -118,21 +120,22 @@ module Astronoby
|
|
|
118
120
|
#
|
|
119
121
|
# @return [Numeric] Delta T in seconds
|
|
120
122
|
def delta_t
|
|
121
|
-
|
|
123
|
+
@memo[:delta_t] ||=
|
|
124
|
+
Util::Time.terrestrial_universal_time_delta(@terrestrial_time)
|
|
122
125
|
end
|
|
123
126
|
|
|
124
127
|
# Get the Greenwich Mean Sidereal Time
|
|
125
128
|
#
|
|
126
129
|
# @return [Numeric] the sidereal time in hours
|
|
127
130
|
def gmst
|
|
128
|
-
GreenwichMeanSiderealTime.from_utc(to_time).time
|
|
131
|
+
@memo[:gmst] ||= GreenwichMeanSiderealTime.from_utc(to_time).time
|
|
129
132
|
end
|
|
130
133
|
|
|
131
134
|
# Get the Greenwich Apparent Sidereal Time
|
|
132
135
|
#
|
|
133
136
|
# @return [Numeric] the sidereal time in hours
|
|
134
137
|
def gast
|
|
135
|
-
GreenwichApparentSiderealTime.from_utc(to_time).time
|
|
138
|
+
@memo[:gast] ||= GreenwichApparentSiderealTime.from_utc(to_time).time
|
|
136
139
|
end
|
|
137
140
|
|
|
138
141
|
# Get the Local Mean Sidereal Time
|
|
@@ -155,7 +158,7 @@ module Astronoby
|
|
|
155
158
|
#
|
|
156
159
|
# @return [Numeric] TAI as Julian Date
|
|
157
160
|
def tai
|
|
158
|
-
@terrestrial_time -
|
|
161
|
+
@memo[:tai] ||= @terrestrial_time -
|
|
159
162
|
Rational(Constants::TAI_TT_OFFSET, Constants::SECONDS_PER_DAY)
|
|
160
163
|
end
|
|
161
164
|
|
|
@@ -167,7 +170,7 @@ module Astronoby
|
|
|
167
170
|
# This is technically false, there is a slight difference between TT and
|
|
168
171
|
# TDB. However, this difference is so small that currenly Astronoby
|
|
169
172
|
# doesn't support it and consider they are the same value.
|
|
170
|
-
@terrestrial_time
|
|
173
|
+
@memo[:tdb] ||= @terrestrial_time
|
|
171
174
|
end
|
|
172
175
|
|
|
173
176
|
# Get the offset between TT and UTC for this instant
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# Geocentric libration of the Moon: the small oscillations that let an
|
|
5
|
+
# observer on Earth see slightly more than half of the lunar surface over
|
|
6
|
+
# time. Holds the total (optical + physical) libration in longitude and
|
|
7
|
+
# latitude.
|
|
8
|
+
class Libration
|
|
9
|
+
# @return [Astronoby::Angle] libration in longitude (l), positive towards
|
|
10
|
+
# the Moon's Mare Crisium (east limb)
|
|
11
|
+
attr_reader :longitude
|
|
12
|
+
|
|
13
|
+
# @return [Astronoby::Angle] libration in latitude (b), positive towards
|
|
14
|
+
# the Moon's north pole
|
|
15
|
+
attr_reader :latitude
|
|
16
|
+
|
|
17
|
+
# @param longitude [Astronoby::Angle] libration in longitude
|
|
18
|
+
# @param latitude [Astronoby::Angle] libration in latitude
|
|
19
|
+
def initialize(longitude:, latitude:)
|
|
20
|
+
@longitude = longitude
|
|
21
|
+
@latitude = latitude
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
# as these coefficients work with TT (Terrestrial Time).
|
|
5
5
|
|
|
6
6
|
module Astronoby
|
|
7
|
+
# Computes the mean obliquity of the ecliptic using the IAU 2006 P03 model.
|
|
7
8
|
class MeanObliquity
|
|
8
9
|
# Source:
|
|
9
10
|
# IAU resolution in 2006 in favor of the P03 astronomical model
|
|
@@ -12,6 +13,10 @@ module Astronoby
|
|
|
12
13
|
EPOCH_OF_REFERENCE = JulianDate::DEFAULT_EPOCH
|
|
13
14
|
OBLIQUITY_OF_REFERENCE = 23.4392794
|
|
14
15
|
|
|
16
|
+
# Computes the mean obliquity of the ecliptic at the given instant.
|
|
17
|
+
#
|
|
18
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
19
|
+
# @return [Astronoby::Angle] the mean obliquity
|
|
15
20
|
def self.at(instant)
|
|
16
21
|
return obliquity_of_reference if instant.julian_date == EPOCH_OF_REFERENCE
|
|
17
22
|
|
|
@@ -32,10 +37,13 @@ module Astronoby
|
|
|
32
37
|
)
|
|
33
38
|
end
|
|
34
39
|
|
|
40
|
+
# @return [Astronoby::Angle] the mean obliquity at the reference epoch
|
|
41
|
+
# (J2000.0)
|
|
35
42
|
def self.obliquity_of_reference
|
|
36
43
|
Angle.from_degree_arcseconds(obliquity_of_reference_in_arcseconds)
|
|
37
44
|
end
|
|
38
45
|
|
|
46
|
+
# @return [Float] the mean obliquity at J2000.0 in arcseconds
|
|
39
47
|
def self.obliquity_of_reference_in_arcseconds
|
|
40
48
|
84381.406
|
|
41
49
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# Computes the Moon's libration and the position angle of its axis from a
|
|
5
|
+
# binary PCK lunar orientation kernel, as the arcsecond-accurate counterpart
|
|
6
|
+
# to the analytic Meeus series in MoonPhysicalEphemeris.
|
|
7
|
+
#
|
|
8
|
+
# The libration is the selenographic longitude and latitude of the sub-Earth
|
|
9
|
+
# point, expressed in the Moon's mean-Earth body-fixed frame.
|
|
10
|
+
class MoonOrientationEphemeris
|
|
11
|
+
# @param moon [Astronoby::Moon] the Moon, carrying an orientation kernel
|
|
12
|
+
def initialize(moon)
|
|
13
|
+
@moon = moon
|
|
14
|
+
@instant = moon.instant
|
|
15
|
+
@orientation = moon.orientation
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @return [Astronoby::Libration] the libration in longitude and latitude
|
|
19
|
+
def libration
|
|
20
|
+
sub_earth = rotation * moon_to_earth
|
|
21
|
+
Libration.new(
|
|
22
|
+
longitude: Angle.from_radians(Math.atan2(sub_earth[1], sub_earth[0])),
|
|
23
|
+
latitude: Angle.from_radians(
|
|
24
|
+
Math.asin(sub_earth[2] / sub_earth.magnitude)
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Position angle of the Moon's axis of rotation, measured eastward from the
|
|
30
|
+
# north point of the disk.
|
|
31
|
+
# @return [Astronoby::Angle] the position angle of the axis
|
|
32
|
+
def position_angle_of_axis
|
|
33
|
+
@moon.apparent.equatorial.position_angle_to(north_pole)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def rotation
|
|
39
|
+
@rotation ||= @orientation.rotation_for(retarded_instant)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def retarded_instant
|
|
43
|
+
Instant.from_terrestrial_time(
|
|
44
|
+
@instant.terrestrial_time - light_time_in_days
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def light_time_in_days
|
|
49
|
+
@moon.astrometric.distance.km /
|
|
50
|
+
Velocity.light_speed.kmps /
|
|
51
|
+
Constants::SECONDS_PER_DAY
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def moon_to_earth
|
|
55
|
+
x, y, z = @moon.astrometric.position.map(&:m).to_a
|
|
56
|
+
::Vector[-x, -y, -z]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def north_pole
|
|
60
|
+
@north_pole ||= Coordinates::Equatorial.from_position_vector(
|
|
61
|
+
Distance.vector_from_meters(
|
|
62
|
+
Nutation.matrix_for(@instant) *
|
|
63
|
+
Precession.matrix_for(@instant) *
|
|
64
|
+
(rotation.transpose * ::Vector[0, 0, 1])
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# Computes the ephemeris for physical observations of the Moon: the total
|
|
5
|
+
# (optical + physical) libration in longitude and latitude, and the position
|
|
6
|
+
# angle of the Moon's axis of rotation.
|
|
7
|
+
#
|
|
8
|
+
# Source:
|
|
9
|
+
# Title: Astronomical Algorithms
|
|
10
|
+
# Author: Jean Meeus
|
|
11
|
+
# Edition: 2nd edition
|
|
12
|
+
# Chapter: 53 - Ephemeris for Physical Observations of the Moon
|
|
13
|
+
class MoonPhysicalEphemeris
|
|
14
|
+
# Inclination of the mean lunar equator to the ecliptic
|
|
15
|
+
INCLINATION = Angle.from_degrees(1.54242)
|
|
16
|
+
|
|
17
|
+
# @param moon [Astronoby::Moon] the Moon at the desired instant
|
|
18
|
+
def initialize(moon)
|
|
19
|
+
@moon = moon
|
|
20
|
+
@instant = moon.instant
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @return [Astronoby::Libration] the total libration in longitude and
|
|
24
|
+
# latitude
|
|
25
|
+
def libration
|
|
26
|
+
Libration.new(
|
|
27
|
+
longitude: Angle.from_degrees(libration_in_longitude),
|
|
28
|
+
latitude: Angle.from_degrees(libration_in_latitude)
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Astronoby::Angle] the position angle of the Moon's axis of
|
|
33
|
+
# rotation, measured eastward from the north point of the disk
|
|
34
|
+
def position_angle_of_axis
|
|
35
|
+
i_rho = INCLINATION + Angle.from_degrees(rho)
|
|
36
|
+
v = ascending_node +
|
|
37
|
+
nutation_in_longitude +
|
|
38
|
+
Angle.from_degrees(sigma / INCLINATION.sin)
|
|
39
|
+
|
|
40
|
+
x = i_rho.sin * v.sin
|
|
41
|
+
y = i_rho.sin * v.cos * obliquity.cos - i_rho.cos * obliquity.sin
|
|
42
|
+
omega = Math.atan2(x, y)
|
|
43
|
+
|
|
44
|
+
Angle.from_radians(
|
|
45
|
+
Math.asin(
|
|
46
|
+
Math.sqrt(x * x + y * y) *
|
|
47
|
+
Math.cos(right_ascension.radians - omega) /
|
|
48
|
+
Angle.from_degrees(libration_in_latitude).cos
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# @return [Float] total libration in longitude, in degrees, in (-180, 180]
|
|
56
|
+
def libration_in_longitude
|
|
57
|
+
l = optical_longitude + physical_longitude
|
|
58
|
+
((l + 180) % 360) - 180
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [Float] total libration in latitude, in degrees
|
|
62
|
+
def libration_in_latitude
|
|
63
|
+
optical_latitude + physical_latitude
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Optical libration in longitude, l' = A - F (Meeus 53.1)
|
|
67
|
+
# @return [Float] in degrees
|
|
68
|
+
def optical_longitude
|
|
69
|
+
argument_a.degrees - argument_of_latitude.degrees
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Optical libration in latitude (Meeus 53.1)
|
|
73
|
+
# @return [Float] in degrees
|
|
74
|
+
def optical_latitude
|
|
75
|
+
Angle.from_radians(optical_latitude_radians).degrees
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Physical libration in longitude (Meeus 53.2)
|
|
79
|
+
# @return [Float] in degrees
|
|
80
|
+
def physical_longitude
|
|
81
|
+
-tau +
|
|
82
|
+
(rho * argument_a.cos + sigma * argument_a.sin) *
|
|
83
|
+
Math.tan(optical_latitude_radians)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Physical libration in latitude (Meeus 53.2)
|
|
87
|
+
# @return [Float] in degrees
|
|
88
|
+
def physical_latitude
|
|
89
|
+
sigma * argument_a.cos - rho * argument_a.sin
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The argument "A" of the optical libration (Meeus 53.1)
|
|
93
|
+
def argument_a
|
|
94
|
+
@argument_a ||= Angle.from_radians(
|
|
95
|
+
Math.atan2(
|
|
96
|
+
w.sin * latitude.cos * INCLINATION.cos -
|
|
97
|
+
latitude.sin * INCLINATION.sin,
|
|
98
|
+
w.cos * latitude.cos
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def optical_latitude_radians
|
|
104
|
+
@optical_latitude_radians ||= Math.asin(
|
|
105
|
+
-w.sin * latitude.cos * INCLINATION.sin - latitude.sin * INCLINATION.cos
|
|
106
|
+
)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# W = λ - Δψ - Ω, the Moon's longitude referred to the mean equinox of
|
|
110
|
+
# date, measured from the ascending node of the mean lunar equator
|
|
111
|
+
def w
|
|
112
|
+
@w ||= longitude - nutation_in_longitude - ascending_node
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Float] periodic terms ρ, in degrees (Meeus 53)
|
|
116
|
+
def rho
|
|
117
|
+
@rho ||= trig_series([
|
|
118
|
+
[-0.02752, mp.radians, :cos],
|
|
119
|
+
[-0.02245, f.radians, :sin],
|
|
120
|
+
[0.00684, (mp - f - f).radians, :cos],
|
|
121
|
+
[-0.00293, (f + f).radians, :cos],
|
|
122
|
+
[-0.00085, (f + f - d - d).radians, :cos],
|
|
123
|
+
[-0.00054, (mp - d - d).radians, :cos],
|
|
124
|
+
[-0.00020, (mp + f).radians, :sin],
|
|
125
|
+
[-0.00020, (mp + f + f).radians, :cos],
|
|
126
|
+
[-0.00020, (mp - f).radians, :cos],
|
|
127
|
+
[0.00014, (mp + f + f - d - d).radians, :cos]
|
|
128
|
+
])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# @return [Float] periodic terms σ, in degrees (Meeus 53)
|
|
132
|
+
def sigma
|
|
133
|
+
@sigma ||= trig_series([
|
|
134
|
+
[-0.02816, mp.radians, :sin],
|
|
135
|
+
[0.02244, f.radians, :cos],
|
|
136
|
+
[-0.00682, (mp - f - f).radians, :sin],
|
|
137
|
+
[-0.00279, (f + f).radians, :sin],
|
|
138
|
+
[-0.00083, (f + f - d - d).radians, :sin],
|
|
139
|
+
[0.00069, (mp - d - d).radians, :sin],
|
|
140
|
+
[0.00040, (mp + f).radians, :cos],
|
|
141
|
+
[-0.00025, (mp + mp).radians, :sin],
|
|
142
|
+
[-0.00023, (mp + f + f).radians, :sin],
|
|
143
|
+
[0.00020, (mp - f).radians, :cos],
|
|
144
|
+
[0.00019, (mp - f).radians, :sin],
|
|
145
|
+
[0.00013, (mp + f + f - d - d).radians, :sin],
|
|
146
|
+
[-0.00010, (mp - f - f - f).radians, :cos]
|
|
147
|
+
])
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @return [Float] periodic terms τ, in degrees (Meeus 53)
|
|
151
|
+
def tau
|
|
152
|
+
@tau ||= trig_series([
|
|
153
|
+
[0.02520 * eccentricity, m.radians, :sin],
|
|
154
|
+
[0.00473, (mp + mp - f - f).radians, :sin],
|
|
155
|
+
[-0.00467, mp.radians, :sin],
|
|
156
|
+
[0.00396, k1.radians, :sin],
|
|
157
|
+
[0.00276, (mp + mp - d - d).radians, :sin],
|
|
158
|
+
[0.00196, ascending_node.radians, :sin],
|
|
159
|
+
[-0.00183, (mp - f).radians, :cos],
|
|
160
|
+
[0.00115, (mp - d - d).radians, :sin],
|
|
161
|
+
[-0.00096, (mp - d).radians, :sin],
|
|
162
|
+
[0.00046, (f + f - d - d).radians, :sin],
|
|
163
|
+
[-0.00039, (mp - f).radians, :sin],
|
|
164
|
+
[-0.00032, (mp - m - d).radians, :sin],
|
|
165
|
+
[0.00027, (mp + mp - m - d - d).radians, :sin],
|
|
166
|
+
[0.00023, k2.radians, :sin],
|
|
167
|
+
[-0.00014, (d + d).radians, :sin],
|
|
168
|
+
[0.00014, (mp + mp - f - f).radians, :cos],
|
|
169
|
+
[-0.00012, (mp - f - f).radians, :sin],
|
|
170
|
+
[-0.00012, (mp + mp).radians, :sin],
|
|
171
|
+
[0.00011, (mp + mp - m - m - d - d).radians, :sin]
|
|
172
|
+
])
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def trig_series(terms)
|
|
176
|
+
terms.sum do |coefficient, argument, fn|
|
|
177
|
+
coefficient * Math.send(fn, argument)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Apparent geocentric ecliptic longitude (true equinox of date, λ)
|
|
182
|
+
def longitude
|
|
183
|
+
@longitude ||= @moon.apparent.ecliptic.longitude
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Apparent geocentric ecliptic latitude (β)
|
|
187
|
+
def latitude
|
|
188
|
+
@latitude ||= @moon.apparent.ecliptic.latitude
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Apparent geocentric right ascension (α)
|
|
192
|
+
def right_ascension
|
|
193
|
+
@right_ascension ||= @moon.apparent.equatorial.right_ascension
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def obliquity
|
|
197
|
+
@obliquity ||= TrueObliquity.at(@instant)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def nutation_in_longitude
|
|
201
|
+
@nutation_in_longitude ||=
|
|
202
|
+
Nutation.new(instant: @instant).nutation_in_longitude
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def t
|
|
206
|
+
@t ||=
|
|
207
|
+
(@instant.tt - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Mean elongation of the Moon (D), Meeus 47.5
|
|
211
|
+
def d
|
|
212
|
+
@d ||= Angle.from_degrees(
|
|
213
|
+
297.8501921 + 445267.1114034 * t - 0.0018819 * t**2 +
|
|
214
|
+
t**3 / 545868 - t**4 / 113065000
|
|
215
|
+
)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Sun's mean anomaly (M), Meeus 47.3
|
|
219
|
+
def m
|
|
220
|
+
@m ||= Angle.from_degrees(
|
|
221
|
+
357.5291092 + 35999.0502909 * t - 0.0001536 * t**2 + t**3 / 24490000
|
|
222
|
+
)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Moon's mean anomaly (M'), Meeus 47.4
|
|
226
|
+
def mp
|
|
227
|
+
@mp ||= Angle.from_degrees(
|
|
228
|
+
134.9633964 + 477198.8675055 * t + 0.0087414 * t**2 +
|
|
229
|
+
t**3 / 69699 - t**4 / 14712000
|
|
230
|
+
)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Moon's argument of latitude (F), Meeus 47.5
|
|
234
|
+
def f
|
|
235
|
+
@f ||= Angle.from_degrees(
|
|
236
|
+
93.2720950 + 483202.0175233 * t - 0.0036539 * t**2 -
|
|
237
|
+
t**3 / 3526000 + t**4 / 863310000
|
|
238
|
+
)
|
|
239
|
+
end
|
|
240
|
+
alias_method :argument_of_latitude, :f
|
|
241
|
+
|
|
242
|
+
# Longitude of the mean ascending node (Ω), Meeus 47.7
|
|
243
|
+
def ascending_node
|
|
244
|
+
@ascending_node ||= Angle.from_degrees(
|
|
245
|
+
125.0445479 - 1934.1362891 * t + 0.0020754 * t**2 +
|
|
246
|
+
t**3 / 467441 - t**4 / 60616000
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Eccentricity correction (E), Meeus 47.6
|
|
251
|
+
def eccentricity
|
|
252
|
+
@eccentricity ||= 1 - 0.002516 * t - 0.0000074 * t**2
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def k1
|
|
256
|
+
@k1 ||= Angle.from_degrees(119.75 + 131.849 * t)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def k2
|
|
260
|
+
@k2 ||= Angle.from_degrees(72.56 + 20.186 * t)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
data/lib/astronoby/nutation.rb
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Computes nutation using the IAU 2000B model (77-term series).
|
|
5
|
+
# Provides both the nutation matrix and the individual nutation angles
|
|
6
|
+
# (in longitude and obliquity).
|
|
4
7
|
class Nutation
|
|
5
8
|
# IAU 2000B model corrections (in microarcseconds)
|
|
6
9
|
IAU2000B_DPSI_CORRECTION = -0.000135e7
|
|
@@ -139,31 +142,18 @@ module Astronoby
|
|
|
139
142
|
dpsi = 0.0
|
|
140
143
|
deps = 0.0
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
arg_coef = term[0..4]
|
|
145
|
+
radians = a.map(&:radians)
|
|
146
|
+
jc = julian_centuries
|
|
145
147
|
|
|
146
|
-
|
|
147
|
-
arg =
|
|
148
|
+
NUTATION_TERMS.each do |term|
|
|
149
|
+
arg = term[0] * radians[0] + term[1] * radians[1] +
|
|
150
|
+
term[2] * radians[2] + term[3] * radians[3] + term[4] * radians[4]
|
|
148
151
|
|
|
149
152
|
sin_arg = Math.sin(arg)
|
|
150
153
|
cos_arg = Math.cos(arg)
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# Extract obliquity coefficients
|
|
156
|
-
obl_coef = term[8..10]
|
|
157
|
-
|
|
158
|
-
# Update dpsi using longitude coefficients
|
|
159
|
-
dpsi += long_coef[0] * sin_arg
|
|
160
|
-
dpsi += long_coef[1] * sin_arg * julian_centuries
|
|
161
|
-
dpsi += long_coef[2] * cos_arg
|
|
162
|
-
|
|
163
|
-
# Update deps using obliquity coefficients
|
|
164
|
-
deps += obl_coef[0] * cos_arg
|
|
165
|
-
deps += obl_coef[1] * cos_arg * julian_centuries
|
|
166
|
-
deps += obl_coef[2] * sin_arg
|
|
155
|
+
dpsi += term[5] * sin_arg + term[6] * sin_arg * jc + term[7] * cos_arg
|
|
156
|
+
deps += term[8] * cos_arg + term[9] * cos_arg * jc + term[10] * sin_arg
|
|
167
157
|
end
|
|
168
158
|
|
|
169
159
|
[dpsi, deps] # in microarcseconds
|