astronoby 0.9.0 → 0.11.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 +235 -0
- data/README.md +19 -1
- data/UPGRADING.md +270 -0
- data/docs/README.md +105 -16
- data/docs/altitude_crossings.md +92 -0
- data/docs/angles.md +1 -0
- data/docs/configuration.md +28 -17
- data/docs/coordinates.md +72 -12
- data/docs/deep_sky_bodies.md +46 -1
- data/docs/ephem.md +5 -2
- data/docs/equinoxes_solstices_times.md +4 -3
- data/docs/glossary.md +99 -1
- data/docs/iers.md +40 -0
- data/docs/instant.md +33 -21
- data/docs/lunar_eclipses.md +208 -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 +43 -24
- 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 +35 -38
- 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/cache.rb +13 -0
- data/lib/astronoby/center.rb +84 -0
- data/lib/astronoby/constants.rb +1 -2
- data/lib/astronoby/constellation.rb +9 -1
- data/lib/astronoby/constellations/data.rb +1 -1
- data/lib/astronoby/coordinates/ecliptic.rb +10 -1
- data/lib/astronoby/coordinates/equatorial.rb +79 -13
- data/lib/astronoby/coordinates/geodetic.rb +102 -0
- data/lib/astronoby/coordinates/horizontal.rb +13 -1
- data/lib/astronoby/delta_t.rb +44 -0
- 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/altitude_crossing.rb +29 -0
- data/lib/astronoby/events/altitude_crossing_calculator.rb +511 -0
- data/lib/astronoby/events/altitude_extreme.rb +31 -0
- data/lib/astronoby/events/conjunction.rb +51 -0
- data/lib/astronoby/events/conjunction_opposition_calculator.rb +84 -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 +111 -0
- data/lib/astronoby/events/lunar_eclipse_calculator.rb +397 -0
- data/lib/astronoby/events/lunar_eclipse_geometry.rb +115 -0
- data/lib/astronoby/events/lunar_eclipse_phase.rb +45 -0
- data/lib/astronoby/events/lunar_eclipse_visibility.rb +296 -0
- data/lib/astronoby/events/opposition.rb +19 -0
- data/lib/astronoby/events/rise_transit_set_calculator.rb +9 -13
- 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_calculator.rb +37 -147
- 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/horizon.rb +40 -0
- data/lib/astronoby/horizontal_transformation.rb +112 -0
- data/lib/astronoby/instant.rb +56 -16
- 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 +62 -61
- data/lib/astronoby/reference_frame.rb +80 -9
- data/lib/astronoby/reference_frames/apparent.rb +43 -12
- 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 +19 -1
- data/lib/astronoby/reference_frames/teme.rb +159 -0
- data/lib/astronoby/reference_frames/topocentric.rb +36 -4
- data/lib/astronoby/refraction.rb +26 -5
- data/lib/astronoby/root_finder.rb +90 -0
- data/lib/astronoby/rotation.rb +49 -0
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +19 -8
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +44 -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 +0 -497
- 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 +30 -0
- metadata +80 -5
|
@@ -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
|
data/lib/astronoby/observer.rb
CHANGED
|
@@ -10,15 +10,26 @@ module Astronoby
|
|
|
10
10
|
MOLAR_MASS_OF_AIR = 0.0289644
|
|
11
11
|
UNIVERSAL_GAS_CONSTANT = 8.31432
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
# @return [Astronoby::Angle] geographic latitude
|
|
14
|
+
attr_reader :latitude
|
|
15
|
+
|
|
16
|
+
# @return [Astronoby::Angle] geographic longitude
|
|
17
|
+
attr_reader :longitude
|
|
18
|
+
|
|
19
|
+
# @return [Astronoby::Distance] geographic elevation above sea level
|
|
20
|
+
attr_reader :elevation
|
|
21
|
+
|
|
22
|
+
# @return [Numeric, String] offset from Coordinated Universal Time
|
|
23
|
+
attr_reader :utc_offset
|
|
24
|
+
|
|
25
|
+
# @return [Numeric] temperature in kelvins
|
|
26
|
+
attr_reader :temperature
|
|
27
|
+
|
|
28
|
+
# @return [Numeric] atmospheric pressure in millibars
|
|
29
|
+
attr_reader :pressure
|
|
30
|
+
|
|
31
|
+
# @param latitude [Astronoby::Angle] geographic latitude of the observer
|
|
32
|
+
# @param longitude [Astronoby::Angle] geographic longitude of the observer
|
|
22
33
|
# @param elevation [Astronoby::Distance] geographic elevation (or altitude)
|
|
23
34
|
# of the observer above sea level
|
|
24
35
|
# @param utc_offset [Numeric, String] offset from Coordinated Universal Time
|
|
@@ -42,45 +53,44 @@ module Astronoby
|
|
|
42
53
|
@pressure = pressure || compute_pressure
|
|
43
54
|
end
|
|
44
55
|
|
|
56
|
+
# Returns the observer's ECEF position vector (WGS-84 geodetic to ECEF).
|
|
57
|
+
#
|
|
58
|
+
# @return [Astronoby::Vector<Astronoby::Distance>] geocentric position
|
|
45
59
|
def geocentric_position
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
@
|
|
51
|
-
|
|
60
|
+
@geocentric_position ||= begin
|
|
61
|
+
n = earth_prime_vertical_radius_of_curvature
|
|
62
|
+
x = (n + @elevation.m) * @latitude.cos * @longitude.cos
|
|
63
|
+
y = (n + @elevation.m) * @latitude.cos * @longitude.sin
|
|
64
|
+
z = (n * (1 - Constants::WGS84_ECCENTICITY_SQUARED) + @elevation.m) *
|
|
65
|
+
@latitude.sin
|
|
66
|
+
Distance.vector_from_meters([x, y, z])
|
|
67
|
+
end
|
|
52
68
|
end
|
|
53
69
|
|
|
70
|
+
# Returns the observer's ECEF velocity vector due to Earth rotation.
|
|
71
|
+
#
|
|
72
|
+
# @return [Astronoby::Vector<Astronoby::Velocity>] geocentric velocity
|
|
54
73
|
def geocentric_velocity
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
74
|
+
@geocentric_velocity ||= begin
|
|
75
|
+
r = projected_radius
|
|
76
|
+
vx = -Constants::EARTH_ANGULAR_VELOCITY_RAD_PER_S * r * @longitude.sin
|
|
77
|
+
vy = Constants::EARTH_ANGULAR_VELOCITY_RAD_PER_S * r * @longitude.cos
|
|
78
|
+
vz = 0.0
|
|
79
|
+
Velocity.vector_from_mps([vx, vy, vz])
|
|
80
|
+
end
|
|
60
81
|
end
|
|
61
82
|
|
|
83
|
+
# Computes the Earth-fixed rotation matrix R₃(GAST) * W (polar motion)
|
|
84
|
+
# for a given instant.
|
|
85
|
+
#
|
|
86
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
87
|
+
# @return [Matrix] 3x3 rotation matrix
|
|
62
88
|
def earth_fixed_rotation_matrix_for(instant)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
mean_obliquity = MeanObliquity.at(instant)
|
|
66
|
-
|
|
67
|
-
gast = Angle.from_radians(
|
|
68
|
-
Angle.from_hours(instant.gmst).radians +
|
|
69
|
-
dpsi.radians * mean_obliquity.cos
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
earth_rotation_matrix = Matrix[
|
|
73
|
-
[gast.cos, -gast.sin, 0],
|
|
74
|
-
[gast.sin, gast.cos, 0],
|
|
75
|
-
[0, 0, 1]
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
nutation_matrix = Nutation.matrix_for(instant)
|
|
79
|
-
precession_matrix = Precession.matrix_for(instant)
|
|
80
|
-
|
|
81
|
-
earth_rotation_matrix * nutation_matrix * precession_matrix
|
|
89
|
+
EarthRotation.matrix_for(instant) * polar_motion_matrix_for(instant)
|
|
82
90
|
end
|
|
83
91
|
|
|
92
|
+
# @param other [Astronoby::Observer] observer to compare with
|
|
93
|
+
# @return [Boolean] true if all attributes are equal
|
|
84
94
|
def ==(other)
|
|
85
95
|
return false unless other.is_a?(self.class)
|
|
86
96
|
|
|
@@ -93,6 +103,7 @@ module Astronoby
|
|
|
93
103
|
end
|
|
94
104
|
alias_method :eql?, :==
|
|
95
105
|
|
|
106
|
+
# @return [Integer] hash value
|
|
96
107
|
def hash
|
|
97
108
|
[
|
|
98
109
|
self.class,
|
|
@@ -107,7 +118,6 @@ module Astronoby
|
|
|
107
118
|
|
|
108
119
|
private
|
|
109
120
|
|
|
110
|
-
# @return [Float] the atmospheric pressure in millibars.
|
|
111
121
|
def compute_pressure
|
|
112
122
|
@pressure ||= PRESSURE_AT_SEA_LEVEL * pressure_ratio
|
|
113
123
|
end
|
|
@@ -124,20 +134,28 @@ module Astronoby
|
|
|
124
134
|
Math.exp(-term1 / term2)
|
|
125
135
|
end
|
|
126
136
|
|
|
137
|
+
def polar_motion_matrix_for(instant)
|
|
138
|
+
rows = IERS::PolarMotion.rotation_matrix_at(instant.to_time)
|
|
139
|
+
Matrix[*rows]
|
|
140
|
+
rescue IERS::OutOfRangeError
|
|
141
|
+
Matrix.identity(3)
|
|
142
|
+
end
|
|
143
|
+
|
|
127
144
|
def earth_prime_vertical_radius_of_curvature
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
145
|
+
@earth_prime_vertical_radius_of_curvature ||=
|
|
146
|
+
Constants::WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS./(
|
|
147
|
+
Math.sqrt(
|
|
148
|
+
1 -
|
|
149
|
+
Constants::WGS84_ECCENTICITY_SQUARED * @latitude.sin * @latitude.sin
|
|
150
|
+
)
|
|
132
151
|
)
|
|
133
|
-
)
|
|
134
152
|
end
|
|
135
153
|
|
|
136
154
|
def projected_radius
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
155
|
+
@projected_radius ||= begin
|
|
156
|
+
pos = geocentric_position
|
|
157
|
+
Math.sqrt(pos.x.m * pos.x.m + pos.y.m * pos.y.m)
|
|
158
|
+
end
|
|
141
159
|
end
|
|
142
160
|
end
|
|
143
161
|
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ephem"
|
|
4
|
+
require "matrix"
|
|
5
|
+
|
|
6
|
+
module Astronoby
|
|
7
|
+
# Wraps a binary PCK lunar orientation kernel (for example
|
|
8
|
+
# +moon_pa_de440_200625.bpc+) and exposes the rotation from the inertial frame
|
|
9
|
+
# (J2000/ICRF) to the Moon's mean-Earth (ME) body-fixed frame at a given
|
|
10
|
+
# instant.
|
|
11
|
+
#
|
|
12
|
+
# A binary kernel only provides the principal-axis (PA) orientation, and which
|
|
13
|
+
# PA frame it describes is read from the kernel itself. JPL Horizons and IMCCE
|
|
14
|
+
# report selenographic positions in the mean-Earth frame, whose small fixed
|
|
15
|
+
# offset from the principal axes is published in the NAIF lunar frame kernels
|
|
16
|
+
# (the +moon_*.tf+ files) rather than in the binary kernel. That alignment is
|
|
17
|
+
# essentially constant across ephemeris versions (below 0.15 arcsecond), so
|
|
18
|
+
# the DE440 values are applied here.
|
|
19
|
+
class Orientation
|
|
20
|
+
# Principal-axis to mean-Earth alignment from the NAIF DE440 lunar frame
|
|
21
|
+
# kernel (moon_de440_250416.tf): the rotation angles about the z, y and x
|
|
22
|
+
# axes that align the principal-axis frame with the mean-Earth frame.
|
|
23
|
+
MEAN_EARTH_ALIGNMENT = [
|
|
24
|
+
Angle.from_degree_arcseconds(67.8526),
|
|
25
|
+
Angle.from_degree_arcseconds(78.6944),
|
|
26
|
+
Angle.from_degree_arcseconds(0.2785)
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
# Download a binary PCK orientation kernel.
|
|
30
|
+
#
|
|
31
|
+
# @param name [String] kernel name supported by the Ephem gem
|
|
32
|
+
# @param target [String] destination path
|
|
33
|
+
# @return [Boolean] true if the download was successful
|
|
34
|
+
def self.download(name:, target:)
|
|
35
|
+
::Ephem::Download.call(name: name, target: target)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Load a binary PCK orientation kernel.
|
|
39
|
+
#
|
|
40
|
+
# @param target [String] path to the +.bpc+ file
|
|
41
|
+
# @return [Astronoby::Orientation]
|
|
42
|
+
# @raise [Astronoby::OrientationError] if the kernel has no orientation data
|
|
43
|
+
def self.load(target)
|
|
44
|
+
new(::Ephem::PCK.open(target))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param pck [::Ephem::PCK] an opened binary PCK
|
|
48
|
+
def initialize(pck)
|
|
49
|
+
@pck = pck
|
|
50
|
+
@source = orientation_source
|
|
51
|
+
@mean_earth_rotation = mean_earth_rotation
|
|
52
|
+
@start_jd = @pck.segments.map(&:start_jd).min
|
|
53
|
+
@end_jd = @pck.segments.map(&:end_jd).max
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Rotation from the inertial frame (J2000/ICRF) to the Moon's mean-Earth
|
|
57
|
+
# body-fixed frame.
|
|
58
|
+
#
|
|
59
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
60
|
+
# @return [Matrix] a 3x3 rotation matrix
|
|
61
|
+
# @raise [Astronoby::OrientationOutOfRangeError] if the kernel does not
|
|
62
|
+
# cover the instant
|
|
63
|
+
def rotation_for(instant)
|
|
64
|
+
@mean_earth_rotation * principal_axis_rotation(instant)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @return [void]
|
|
68
|
+
def close
|
|
69
|
+
@pck.close
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# The orientation source for the body frame the kernel describes, read from
|
|
75
|
+
# the kernel rather than assumed.
|
|
76
|
+
def orientation_source
|
|
77
|
+
segment = @pck.segments.first
|
|
78
|
+
raise OrientationError, "Orientation kernel has no segments" unless segment
|
|
79
|
+
|
|
80
|
+
@pck[segment.target]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Inertial -> principal-axis rotation from the binary kernel's Euler angles.
|
|
84
|
+
def principal_axis_rotation(instant)
|
|
85
|
+
terrestrial_time = instant.terrestrial_time
|
|
86
|
+
unless terrestrial_time.between?(@start_jd, @end_jd)
|
|
87
|
+
raise OrientationOutOfRangeError,
|
|
88
|
+
"Orientation kernel covers #{date(@start_jd)} to #{date(@end_jd)}; " \
|
|
89
|
+
"requested #{date(terrestrial_time)}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Matrix[*@source.matrix_at(terrestrial_time)]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Principal-axis -> mean-Earth rotation (constant), about the x, y, z axes.
|
|
96
|
+
def mean_earth_rotation
|
|
97
|
+
about_z, about_y, about_x = MEAN_EARTH_ALIGNMENT
|
|
98
|
+
Rotation.about_x(-about_x) *
|
|
99
|
+
Rotation.about_y(-about_y) *
|
|
100
|
+
Rotation.about_z(-about_z)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def date(terrestrial_time)
|
|
104
|
+
Instant.from_terrestrial_time(terrestrial_time).to_time.utc.to_date
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
module Position
|
|
5
|
+
# @param observer [Astronoby::Observer] the observer
|
|
6
|
+
# @return [Astronoby::Topocentric] the topocentric reference frame
|
|
7
|
+
def observed_by(observer)
|
|
8
|
+
Topocentric.build_from_apparent(
|
|
9
|
+
apparent: apparent,
|
|
10
|
+
observer: observer,
|
|
11
|
+
instant: instant,
|
|
12
|
+
target_body: body
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|