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
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
|
data/lib/astronoby/precession.rb
CHANGED
|
@@ -1,88 +1,89 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Computes precession matrices using the IAU 2006 Fukushima-Williams
|
|
5
|
+
# angles with ICRS frame bias. Also provides coordinate precession
|
|
6
|
+
# using the classical method.
|
|
4
7
|
class Precession
|
|
8
|
+
# Computes the combined precession-bias matrix for a given instant.
|
|
9
|
+
#
|
|
10
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
11
|
+
# @return [Matrix] the 3x3 precession-bias matrix PB(t)
|
|
5
12
|
def self.matrix_for(instant)
|
|
6
13
|
new(instant: instant).matrix
|
|
7
14
|
end
|
|
8
15
|
|
|
16
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
9
17
|
def initialize(instant:)
|
|
10
18
|
@instant = instant
|
|
11
19
|
end
|
|
12
20
|
|
|
21
|
+
# Computes the combined precession-bias matrix PB(t) = R1(-εA) R3(-ψ̄) R1(φ̄) R3(γ̄)
|
|
22
|
+
#
|
|
23
|
+
# @return [Matrix] the 3x3 precession-bias matrix
|
|
13
24
|
def matrix
|
|
25
|
+
# Fukushima-Williams precession angles including frame bias.
|
|
14
26
|
# Source:
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
27
|
+
# IERS Conventions 2010, Section 5.6.4
|
|
28
|
+
# Wallace & Capitaine (2006), A&A 459, 981
|
|
29
|
+
# ERFA eraPfw06 / eraFw2m
|
|
30
|
+
# PB(t) = R1(−εA) R3(−ψ̄) R1(φ̄) R3(γ̄)
|
|
18
31
|
|
|
19
32
|
cache.fetch(cache_key) do
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-0.
|
|
40
|
-
+
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
r3_chi = rotation_z(chi_a)
|
|
52
|
-
r1_eps0 = rotation_x(MeanObliquity.obliquity_of_reference)
|
|
53
|
-
|
|
54
|
-
r3_chi * r1_omega * r3_psi * r1_eps0
|
|
33
|
+
gamma_bar = ((((
|
|
34
|
+
+0.0000000260 * t +
|
|
35
|
+
-0.000002788) * t +
|
|
36
|
+
-0.00031238) * t +
|
|
37
|
+
+0.4932044) * t +
|
|
38
|
+
+10.556378) * t +
|
|
39
|
+
-0.052928
|
|
40
|
+
|
|
41
|
+
phi_bar = ((((
|
|
42
|
+
-0.0000000176 * t +
|
|
43
|
+
-0.000000440) * t +
|
|
44
|
+
+0.00053289) * t +
|
|
45
|
+
+0.0511268) * t +
|
|
46
|
+
-46.811016) * t +
|
|
47
|
+
+84381.412819
|
|
48
|
+
|
|
49
|
+
psi_bar = ((((
|
|
50
|
+
-0.0000000148 * t +
|
|
51
|
+
-0.000026452) * t +
|
|
52
|
+
-0.00018522) * t +
|
|
53
|
+
+1.5584175) * t +
|
|
54
|
+
+5038.481484) * t +
|
|
55
|
+
-0.041775
|
|
56
|
+
|
|
57
|
+
gamma_bar = Angle.from_degree_arcseconds(gamma_bar)
|
|
58
|
+
phi_bar = Angle.from_degree_arcseconds(phi_bar)
|
|
59
|
+
psi_bar = Angle.from_degree_arcseconds(psi_bar)
|
|
60
|
+
eps_a = MeanObliquity.at(@instant)
|
|
61
|
+
|
|
62
|
+
Rotation.about_x(-eps_a) * Rotation.about_z(-psi_bar) *
|
|
63
|
+
Rotation.about_x(phi_bar) * Rotation.about_z(gamma_bar)
|
|
55
64
|
end
|
|
56
65
|
end
|
|
57
66
|
|
|
58
|
-
def rotation_x(angle)
|
|
59
|
-
c, s = angle.cos, angle.sin
|
|
60
|
-
Matrix[
|
|
61
|
-
[1, 0, 0],
|
|
62
|
-
[0, c, s],
|
|
63
|
-
[0, -s, c]
|
|
64
|
-
]
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def rotation_z(angle)
|
|
68
|
-
c, s = angle.cos, angle.sin
|
|
69
|
-
Matrix[
|
|
70
|
-
[c, s, 0],
|
|
71
|
-
[-s, c, 0],
|
|
72
|
-
[0, 0, 1]
|
|
73
|
-
]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
67
|
# Source:
|
|
77
68
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
78
69
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
79
70
|
# Edition: Cambridge University Press
|
|
80
71
|
# Chapter: 34 - Precession
|
|
81
72
|
|
|
73
|
+
# Precesses equatorial coordinates from their current epoch to a new epoch.
|
|
74
|
+
#
|
|
75
|
+
# @param coordinates [Astronoby::Coordinates::Equatorial] coordinates to
|
|
76
|
+
# precess
|
|
77
|
+
# @param epoch [Numeric] the target Julian Date epoch
|
|
78
|
+
# @return [Astronoby::Coordinates::Equatorial] precessed coordinates
|
|
82
79
|
def self.for_equatorial_coordinates(coordinates:, epoch:)
|
|
83
80
|
precess(coordinates, epoch)
|
|
84
81
|
end
|
|
85
82
|
|
|
83
|
+
# @param coordinates [Astronoby::Coordinates::Equatorial] coordinates to
|
|
84
|
+
# precess
|
|
85
|
+
# @param epoch [Numeric] the target Julian Date epoch
|
|
86
|
+
# @return [Astronoby::Coordinates::Equatorial] precessed coordinates
|
|
86
87
|
def self.precess(coordinates, epoch)
|
|
87
88
|
matrix_a = matrix_for_epoch(coordinates.epoch)
|
|
88
89
|
matrix_b = matrix_for_epoch(epoch).transpose
|
|
@@ -107,6 +108,10 @@ module Astronoby
|
|
|
107
108
|
)
|
|
108
109
|
end
|
|
109
110
|
|
|
111
|
+
# Computes the classical precession matrix for a given epoch.
|
|
112
|
+
#
|
|
113
|
+
# @param epoch [Numeric] a Julian Date epoch
|
|
114
|
+
# @return [Matrix] 3x3 precession matrix
|
|
110
115
|
def self.matrix_for_epoch(epoch)
|
|
111
116
|
t = (epoch - JulianDate::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
|
|
112
117
|
|
|
@@ -162,9 +167,5 @@ module Astronoby
|
|
|
162
167
|
Constants::DAYS_PER_JULIAN_CENTURY
|
|
163
168
|
)
|
|
164
169
|
end
|
|
165
|
-
|
|
166
|
-
def eps0
|
|
167
|
-
@eps0 ||= MeanObliquity.obliquity_of_reference_in_arcseconds
|
|
168
|
-
end
|
|
169
170
|
end
|
|
170
171
|
end
|
|
@@ -1,27 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Base class for reference frames in the astronomical reference frame chain.
|
|
5
|
+
# Each frame represents a body's position and velocity relative to a center,
|
|
6
|
+
# at a specific instant.
|
|
4
7
|
class ReferenceFrame
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
:instant,
|
|
8
|
-
:center_identifier,
|
|
9
|
-
:target_body
|
|
8
|
+
# @return [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
9
|
+
attr_reader :position
|
|
10
10
|
|
|
11
|
+
# @return [Astronoby::Vector<Astronoby::Velocity>] velocity vector
|
|
12
|
+
attr_reader :velocity
|
|
13
|
+
|
|
14
|
+
# @return [Astronoby::Instant] the time instant
|
|
15
|
+
attr_reader :instant
|
|
16
|
+
|
|
17
|
+
# @return [Astronoby::Center] the center of the frame
|
|
18
|
+
attr_reader :center
|
|
19
|
+
|
|
20
|
+
# @return [Astronoby::Body, nil] the target body
|
|
21
|
+
attr_reader :target_body
|
|
22
|
+
|
|
23
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
24
|
+
# @param velocity [Astronoby::Vector<Astronoby::Velocity>] velocity vector
|
|
25
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
26
|
+
# @param center [Astronoby::Center] the center of the frame
|
|
27
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
11
28
|
def initialize(
|
|
12
29
|
position:,
|
|
13
30
|
velocity:,
|
|
14
31
|
instant:,
|
|
15
|
-
|
|
32
|
+
center:,
|
|
16
33
|
target_body:
|
|
17
34
|
)
|
|
18
35
|
@position = position
|
|
19
36
|
@velocity = velocity
|
|
20
37
|
@instant = instant
|
|
21
|
-
@
|
|
38
|
+
@center = center
|
|
22
39
|
@target_body = target_body
|
|
23
40
|
end
|
|
24
41
|
|
|
42
|
+
# @return [Astronoby::Coordinates::Equatorial] equatorial coordinates
|
|
43
|
+
# derived from the position vector
|
|
25
44
|
def equatorial
|
|
26
45
|
@equatorial ||= begin
|
|
27
46
|
return Coordinates::Equatorial.zero if distance.zero?
|
|
@@ -30,6 +49,8 @@ module Astronoby
|
|
|
30
49
|
end
|
|
31
50
|
end
|
|
32
51
|
|
|
52
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates derived
|
|
53
|
+
# from the equatorial coordinates at J2000.0
|
|
33
54
|
def ecliptic
|
|
34
55
|
@ecliptic ||= begin
|
|
35
56
|
return Coordinates::Ecliptic.zero if distance.zero?
|
|
@@ -39,6 +60,7 @@ module Astronoby
|
|
|
39
60
|
end
|
|
40
61
|
end
|
|
41
62
|
|
|
63
|
+
# @return [Astronoby::Distance] the Euclidean distance from the center
|
|
42
64
|
def distance
|
|
43
65
|
@distance ||= begin
|
|
44
66
|
return Distance.zero if @position.zero?
|
|
@@ -46,5 +68,49 @@ module Astronoby
|
|
|
46
68
|
@position.magnitude
|
|
47
69
|
end
|
|
48
70
|
end
|
|
71
|
+
|
|
72
|
+
# @param other [Astronoby::ReferenceFrame] another frame at the same stage
|
|
73
|
+
# and instant
|
|
74
|
+
# @return [Astronoby::Angle] the angular separation, between 0° and 180°
|
|
75
|
+
# @raise [Astronoby::IncompatibleArgumentsError] if the frames cannot be
|
|
76
|
+
# meaningfully compared
|
|
77
|
+
def separation_from(other)
|
|
78
|
+
ensure_comparable!(other)
|
|
79
|
+
return Angle.zero if @position.zero? || other.position.zero?
|
|
80
|
+
|
|
81
|
+
position_vector = @position.map(&:m)
|
|
82
|
+
other_position_vector = other.position.map(&:m)
|
|
83
|
+
cross = Util::Maths.cross_product(position_vector, other_position_vector)
|
|
84
|
+
cross_magnitude = Math.sqrt(Util::Maths.dot_product(cross, cross))
|
|
85
|
+
dot = Util::Maths.dot_product(position_vector, other_position_vector)
|
|
86
|
+
|
|
87
|
+
Angle.from_radians(Math.atan2(cross_magnitude, dot))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def ensure_comparable!(other)
|
|
93
|
+
unless other.is_a?(ReferenceFrame)
|
|
94
|
+
raise IncompatibleArgumentsError,
|
|
95
|
+
"Expected an Astronoby::ReferenceFrame, got #{other.class}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
unless instance_of?(other.class)
|
|
99
|
+
raise IncompatibleArgumentsError,
|
|
100
|
+
"Cannot compute the separation between different reference frames " \
|
|
101
|
+
"(#{self.class} and #{other.class}); both frames must be at the " \
|
|
102
|
+
"same stage of the reference frame chain"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
unless instant == other.instant
|
|
106
|
+
raise IncompatibleArgumentsError,
|
|
107
|
+
"Cannot compute the separation between frames at different instants"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
unless center == other.center
|
|
111
|
+
raise IncompatibleArgumentsError,
|
|
112
|
+
"Cannot compute the separation between frames with different centers"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
49
115
|
end
|
|
50
116
|
end
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Apparent reference frame. Represents a body's geocentric position as it
|
|
5
|
+
# appears from Earth, corrected for aberration, precession, and nutation.
|
|
4
6
|
class Apparent < ReferenceFrame
|
|
7
|
+
# Builds an apparent frame from an astrometric frame by applying
|
|
8
|
+
# aberration in GCRS, then rotating by nutation * precession (with ICRS
|
|
9
|
+
# frame bias).
|
|
10
|
+
#
|
|
11
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
12
|
+
# @param target_astrometric [Astronoby::Astrometric] target's astrometric
|
|
13
|
+
# frame
|
|
14
|
+
# @param earth_geometric [Astronoby::Geometric] Earth's geometric frame
|
|
15
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
16
|
+
# @return [Astronoby::Apparent] a new apparent frame
|
|
5
17
|
def self.build_from_astrometric(
|
|
6
18
|
instant:,
|
|
7
19
|
target_astrometric:,
|
|
@@ -13,37 +25,44 @@ module Astronoby
|
|
|
13
25
|
precession_matrix = Precession.matrix_for(instant)
|
|
14
26
|
nutation_matrix = Nutation.matrix_for(instant)
|
|
15
27
|
|
|
16
|
-
corrected_position = Distance.vector_from_meters(
|
|
17
|
-
precession_matrix * nutation_matrix * position.map(&:m)
|
|
18
|
-
)
|
|
19
28
|
corrected_position = Aberration.new(
|
|
20
|
-
astrometric_position:
|
|
29
|
+
astrometric_position: position,
|
|
21
30
|
observer_velocity: earth_geometric.velocity
|
|
22
31
|
).corrected_position
|
|
32
|
+
|
|
23
33
|
# In theory, here we should also apply light deflection. However, so far
|
|
24
34
|
# the deflection algorithm hasn't shown any significant changes to the
|
|
25
35
|
# apparent position. Therefore, for now, we are saving some computation
|
|
26
36
|
# time by not applying it, and we will investigate if the algorithm is
|
|
27
37
|
# correct or if the deflection is indeed negligible.
|
|
28
38
|
|
|
39
|
+
corrected_position = Distance.vector_from_meters(
|
|
40
|
+
nutation_matrix * precession_matrix * corrected_position.map(&:m)
|
|
41
|
+
)
|
|
42
|
+
|
|
29
43
|
corrected_velocity = Velocity.vector_from_mps(
|
|
30
|
-
|
|
44
|
+
nutation_matrix * precession_matrix * velocity.map(&:mps)
|
|
31
45
|
)
|
|
32
46
|
|
|
33
47
|
new(
|
|
34
48
|
position: corrected_position,
|
|
35
49
|
velocity: corrected_velocity,
|
|
36
50
|
instant: instant,
|
|
37
|
-
|
|
51
|
+
center: Center.geocentric,
|
|
38
52
|
target_body: target_body
|
|
39
53
|
)
|
|
40
54
|
end
|
|
41
55
|
|
|
56
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates at the
|
|
57
|
+
# current instant (true ecliptic and equinox of date)
|
|
42
58
|
def ecliptic
|
|
43
59
|
@ecliptic ||= begin
|
|
44
60
|
return Coordinates::Ecliptic.zero if distance.zero?
|
|
45
61
|
|
|
46
|
-
equatorial.to_ecliptic(
|
|
62
|
+
equatorial.to_ecliptic(
|
|
63
|
+
instant: @instant,
|
|
64
|
+
obliquity: TrueObliquity.at(@instant)
|
|
65
|
+
)
|
|
47
66
|
end
|
|
48
67
|
end
|
|
49
68
|
end
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Astrometric reference frame (GCRS). Represents a body's position relative
|
|
5
|
+
# to the Earth, corrected for light-time delay.
|
|
4
6
|
class Astrometric < ReferenceFrame
|
|
7
|
+
# Builds an astrometric frame from geometric frames with light-time
|
|
8
|
+
# correction.
|
|
9
|
+
#
|
|
10
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
11
|
+
# @param earth_geometric [Astronoby::Geometric] Earth's geometric frame
|
|
12
|
+
# @param light_time_corrected_position [Astronoby::Vector<Astronoby::Distance>]
|
|
13
|
+
# target position corrected for light-time delay
|
|
14
|
+
# @param light_time_corrected_velocity [Astronoby::Vector<Astronoby::Velocity>]
|
|
15
|
+
# target velocity corrected for light-time delay
|
|
16
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
17
|
+
# @return [Astronoby::Astrometric] a new astrometric frame
|
|
5
18
|
def self.build_from_geometric(
|
|
6
19
|
instant:,
|
|
7
20
|
earth_geometric:,
|
|
@@ -13,7 +26,7 @@ module Astronoby
|
|
|
13
26
|
position: light_time_corrected_position - earth_geometric.position,
|
|
14
27
|
velocity: light_time_corrected_velocity - earth_geometric.velocity,
|
|
15
28
|
instant: instant,
|
|
16
|
-
|
|
29
|
+
center: Center.geocentric,
|
|
17
30
|
target_body: target_body
|
|
18
31
|
)
|
|
19
32
|
end
|