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
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
|
|
|
@@ -158,13 +163,9 @@ module Astronoby
|
|
|
158
163
|
|
|
159
164
|
def t
|
|
160
165
|
@t ||= Rational(
|
|
161
|
-
@instant.
|
|
166
|
+
@instant.tt - JulianDate::DEFAULT_EPOCH,
|
|
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,35 +1,61 @@
|
|
|
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 [Numeric] Julian Date epoch
|
|
43
|
+
def epoch
|
|
44
|
+
JulianDate::DEFAULT_EPOCH
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Astronoby::Coordinates::Equatorial] equatorial coordinates
|
|
48
|
+
# derived from the position vector, referred to the frame's epoch
|
|
25
49
|
def equatorial
|
|
26
50
|
@equatorial ||= begin
|
|
27
|
-
return Coordinates::Equatorial.zero if distance.zero?
|
|
51
|
+
return Coordinates::Equatorial.zero(epoch: epoch) if distance.zero?
|
|
28
52
|
|
|
29
|
-
Coordinates::Equatorial.from_position_vector(@position)
|
|
53
|
+
Coordinates::Equatorial.from_position_vector(@position, epoch: epoch)
|
|
30
54
|
end
|
|
31
55
|
end
|
|
32
56
|
|
|
57
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates derived
|
|
58
|
+
# from the equatorial coordinates at J2000.0
|
|
33
59
|
def ecliptic
|
|
34
60
|
@ecliptic ||= begin
|
|
35
61
|
return Coordinates::Ecliptic.zero if distance.zero?
|
|
@@ -39,6 +65,7 @@ module Astronoby
|
|
|
39
65
|
end
|
|
40
66
|
end
|
|
41
67
|
|
|
68
|
+
# @return [Astronoby::Distance] the Euclidean distance from the center
|
|
42
69
|
def distance
|
|
43
70
|
@distance ||= begin
|
|
44
71
|
return Distance.zero if @position.zero?
|
|
@@ -46,5 +73,49 @@ module Astronoby
|
|
|
46
73
|
@position.magnitude
|
|
47
74
|
end
|
|
48
75
|
end
|
|
76
|
+
|
|
77
|
+
# @param other [Astronoby::ReferenceFrame] another frame at the same stage
|
|
78
|
+
# and instant
|
|
79
|
+
# @return [Astronoby::Angle] the angular separation, between 0° and 180°
|
|
80
|
+
# @raise [Astronoby::IncompatibleArgumentsError] if the frames cannot be
|
|
81
|
+
# meaningfully compared
|
|
82
|
+
def separation_from(other)
|
|
83
|
+
ensure_comparable!(other)
|
|
84
|
+
return Angle.zero if @position.zero? || other.position.zero?
|
|
85
|
+
|
|
86
|
+
position_vector = @position.map(&:m)
|
|
87
|
+
other_position_vector = other.position.map(&:m)
|
|
88
|
+
cross = Util::Maths.cross_product(position_vector, other_position_vector)
|
|
89
|
+
cross_magnitude = Math.sqrt(Util::Maths.dot_product(cross, cross))
|
|
90
|
+
dot = Util::Maths.dot_product(position_vector, other_position_vector)
|
|
91
|
+
|
|
92
|
+
Angle.from_radians(Math.atan2(cross_magnitude, dot))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def ensure_comparable!(other)
|
|
98
|
+
unless other.is_a?(ReferenceFrame)
|
|
99
|
+
raise IncompatibleArgumentsError,
|
|
100
|
+
"Expected an Astronoby::ReferenceFrame, got #{other.class}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
unless instance_of?(other.class)
|
|
104
|
+
raise IncompatibleArgumentsError,
|
|
105
|
+
"Cannot compute the separation between different reference frames " \
|
|
106
|
+
"(#{self.class} and #{other.class}); both frames must be at the " \
|
|
107
|
+
"same stage of the reference frame chain"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
unless instant == other.instant
|
|
111
|
+
raise IncompatibleArgumentsError,
|
|
112
|
+
"Cannot compute the separation between frames at different instants"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
unless center == other.center
|
|
116
|
+
raise IncompatibleArgumentsError,
|
|
117
|
+
"Cannot compute the separation between frames with different centers"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
49
120
|
end
|
|
50
121
|
end
|
|
@@ -1,49 +1,80 @@
|
|
|
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). Without Earth's geometric frame there is no observer
|
|
10
|
+
# velocity to derive the aberration from, so only the rotation is applied.
|
|
11
|
+
#
|
|
12
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
13
|
+
# @param target_astrometric [Astronoby::Astrometric] target's astrometric
|
|
14
|
+
# frame
|
|
15
|
+
# @param earth_geometric [Astronoby::Geometric, nil] Earth's geometric
|
|
16
|
+
# frame
|
|
17
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
18
|
+
# @return [Astronoby::Apparent] a new apparent frame
|
|
5
19
|
def self.build_from_astrometric(
|
|
6
20
|
instant:,
|
|
7
21
|
target_astrometric:,
|
|
8
|
-
|
|
9
|
-
|
|
22
|
+
target_body:,
|
|
23
|
+
earth_geometric: nil
|
|
10
24
|
)
|
|
11
25
|
position = target_astrometric.position
|
|
12
26
|
velocity = target_astrometric.velocity
|
|
13
27
|
precession_matrix = Precession.matrix_for(instant)
|
|
14
28
|
nutation_matrix = Nutation.matrix_for(instant)
|
|
15
29
|
|
|
16
|
-
corrected_position =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
corrected_position = if earth_geometric
|
|
31
|
+
Aberration.new(
|
|
32
|
+
astrometric_position: position,
|
|
33
|
+
observer_velocity: earth_geometric.velocity
|
|
34
|
+
).corrected_position
|
|
35
|
+
else
|
|
36
|
+
position
|
|
37
|
+
end
|
|
38
|
+
|
|
23
39
|
# In theory, here we should also apply light deflection. However, so far
|
|
24
40
|
# the deflection algorithm hasn't shown any significant changes to the
|
|
25
41
|
# apparent position. Therefore, for now, we are saving some computation
|
|
26
42
|
# time by not applying it, and we will investigate if the algorithm is
|
|
27
43
|
# correct or if the deflection is indeed negligible.
|
|
28
44
|
|
|
45
|
+
corrected_position = Distance.vector_from_meters(
|
|
46
|
+
nutation_matrix * precession_matrix * corrected_position.map(&:m)
|
|
47
|
+
)
|
|
48
|
+
|
|
29
49
|
corrected_velocity = Velocity.vector_from_mps(
|
|
30
|
-
|
|
50
|
+
nutation_matrix * precession_matrix * velocity.map(&:mps)
|
|
31
51
|
)
|
|
32
52
|
|
|
33
53
|
new(
|
|
34
54
|
position: corrected_position,
|
|
35
55
|
velocity: corrected_velocity,
|
|
36
56
|
instant: instant,
|
|
37
|
-
|
|
57
|
+
center: Center.geocentric,
|
|
38
58
|
target_body: target_body
|
|
39
59
|
)
|
|
40
60
|
end
|
|
41
61
|
|
|
62
|
+
# @return [Numeric] Julian Date epoch of the frame's coordinates, which
|
|
63
|
+
# are referred to the true equator and equinox of date
|
|
64
|
+
def epoch
|
|
65
|
+
instant.tt
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates at the
|
|
69
|
+
# current instant (true ecliptic and equinox of date)
|
|
42
70
|
def ecliptic
|
|
43
71
|
@ecliptic ||= begin
|
|
44
72
|
return Coordinates::Ecliptic.zero if distance.zero?
|
|
45
73
|
|
|
46
|
-
equatorial.to_ecliptic(
|
|
74
|
+
equatorial.to_ecliptic(
|
|
75
|
+
instant: @instant,
|
|
76
|
+
obliquity: TrueObliquity.at(@instant)
|
|
77
|
+
)
|
|
47
78
|
end
|
|
48
79
|
end
|
|
49
80
|
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
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Geometric reference frame (BCRS). Represents a body's position relative
|
|
5
|
+
# to the Solar System Barycenter, without any corrections applied.
|
|
4
6
|
class Geometric < ReferenceFrame
|
|
7
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
8
|
+
# @param velocity [Astronoby::Vector<Astronoby::Velocity>] velocity vector
|
|
9
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
10
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
5
11
|
def initialize(
|
|
6
12
|
position:,
|
|
7
13
|
velocity:,
|
|
@@ -12,7 +18,7 @@ module Astronoby
|
|
|
12
18
|
position: position,
|
|
13
19
|
velocity: velocity,
|
|
14
20
|
instant: instant,
|
|
15
|
-
|
|
21
|
+
center: Center.barycentric,
|
|
16
22
|
target_body: target_body
|
|
17
23
|
)
|
|
18
24
|
end
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Mean-of-date reference frame. Represents a body's geocentric position
|
|
5
|
+
# corrected for precession only (no nutation or aberration).
|
|
4
6
|
class MeanOfDate < ReferenceFrame
|
|
7
|
+
# Builds a mean-of-date frame from geometric frames by applying
|
|
8
|
+
# precession.
|
|
9
|
+
#
|
|
10
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
11
|
+
# @param target_geometric [Astronoby::Geometric] target's geometric frame
|
|
12
|
+
# @param earth_geometric [Astronoby::Geometric] Earth's geometric frame
|
|
13
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
14
|
+
# @return [Astronoby::MeanOfDate] a new mean-of-date frame
|
|
5
15
|
def self.build_from_geometric(
|
|
6
16
|
instant:,
|
|
7
17
|
target_geometric:,
|
|
@@ -22,11 +32,19 @@ module Astronoby
|
|
|
22
32
|
position: corrected_position,
|
|
23
33
|
velocity: corrected_velocity,
|
|
24
34
|
instant: instant,
|
|
25
|
-
|
|
35
|
+
center: Center.geocentric,
|
|
26
36
|
target_body: target_body
|
|
27
37
|
)
|
|
28
38
|
end
|
|
29
39
|
|
|
40
|
+
# @return [Numeric] Julian Date epoch of the frame's coordinates, which
|
|
41
|
+
# are referred to the mean equator and equinox of date
|
|
42
|
+
def epoch
|
|
43
|
+
instant.tt
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates at the
|
|
47
|
+
# current instant (mean equinox of date)
|
|
30
48
|
def ecliptic
|
|
31
49
|
@ecliptic ||= begin
|
|
32
50
|
return Coordinates::Ecliptic.zero if distance.zero?
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# TEME (True Equator, Mean Equinox) reference frame. This is the output
|
|
5
|
+
# frame of the SGP4/SDP4 satellite orbit propagators. Provides conversions
|
|
6
|
+
# to ECEF, GCRS, and topocentric frames.
|
|
7
|
+
class Teme < ReferenceFrame
|
|
8
|
+
# ECEF position and velocity vectors.
|
|
9
|
+
class EcefCoordinates
|
|
10
|
+
# @return [Astronoby::Vector<Astronoby::Distance>] ECEF position
|
|
11
|
+
attr_reader :position
|
|
12
|
+
|
|
13
|
+
# @return [Astronoby::Vector<Astronoby::Velocity>] ECEF velocity
|
|
14
|
+
attr_reader :velocity
|
|
15
|
+
|
|
16
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] ECEF position
|
|
17
|
+
# @param velocity [Astronoby::Vector<Astronoby::Velocity>] ECEF velocity
|
|
18
|
+
def initialize(position:, velocity:)
|
|
19
|
+
@position = position
|
|
20
|
+
@velocity = velocity
|
|
21
|
+
freeze
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Converts the ECEF position to WGS-84 geodetic coordinates using
|
|
25
|
+
# Bowring's iterative method.
|
|
26
|
+
#
|
|
27
|
+
# @return [Astronoby::Coordinates::Geodetic] geodetic coordinates
|
|
28
|
+
def geodetic
|
|
29
|
+
Coordinates::Geodetic.from_ecef(@position)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] TEME position
|
|
34
|
+
# @param velocity [Astronoby::Vector<Astronoby::Velocity>] TEME velocity
|
|
35
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
36
|
+
def initialize(position:, velocity:, instant:)
|
|
37
|
+
super(
|
|
38
|
+
position: position,
|
|
39
|
+
velocity: velocity,
|
|
40
|
+
instant: instant,
|
|
41
|
+
center: Center.geocentric,
|
|
42
|
+
target_body: nil
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Numeric] Julian Date epoch of the frame's coordinates, which
|
|
47
|
+
# are referred to the true equator and mean equinox of date
|
|
48
|
+
def epoch
|
|
49
|
+
instant.tt
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Converts TEME position and velocity to ECEF using the canonical
|
|
53
|
+
# Vallado formulation with R₃(GMST).
|
|
54
|
+
#
|
|
55
|
+
# Velocity includes the ω×r transport term to account for Earth
|
|
56
|
+
# rotation.
|
|
57
|
+
#
|
|
58
|
+
# @return [Astronoby::Teme::EcefCoordinates] ECEF position and velocity
|
|
59
|
+
def to_ecef
|
|
60
|
+
mean_rotation_matrix = EarthRotation.mean_matrix_for(@instant).transpose
|
|
61
|
+
|
|
62
|
+
position = mean_rotation_matrix * @position.map(&:m)
|
|
63
|
+
velocity_mps = mean_rotation_matrix * @velocity.map(&:mps)
|
|
64
|
+
|
|
65
|
+
omega = Constants::EARTH_ANGULAR_VELOCITY_RAD_PER_S
|
|
66
|
+
corrected_vel = ::Vector[
|
|
67
|
+
velocity_mps[0] + omega * position[1],
|
|
68
|
+
velocity_mps[1] - omega * position[0],
|
|
69
|
+
velocity_mps[2]
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
EcefCoordinates.new(
|
|
73
|
+
position: Distance.vector_from_meters(position),
|
|
74
|
+
velocity: Velocity.vector_from_mps(corrected_vel)
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Converts TEME to GCRS using the pragmatic approach: reuse existing
|
|
79
|
+
# IAU 2006/2000B precession and nutation matrices transposed.
|
|
80
|
+
#
|
|
81
|
+
# The transformation chain is: r_GCRS = PBᵀ * Nᵀ * R₃(EoE) * r_TEME
|
|
82
|
+
#
|
|
83
|
+
# @return [Astronoby::Astrometric] GCRS Earth-centered frame
|
|
84
|
+
def to_gcrs
|
|
85
|
+
Astrometric.new(
|
|
86
|
+
position: Distance.vector_from_meters(
|
|
87
|
+
gcrs_rotation_matrix * @position.map(&:m)
|
|
88
|
+
),
|
|
89
|
+
velocity: Velocity.vector_from_mps(
|
|
90
|
+
gcrs_rotation_matrix * @velocity.map(&:mps)
|
|
91
|
+
),
|
|
92
|
+
instant: @instant,
|
|
93
|
+
center: Center.geocentric,
|
|
94
|
+
target_body: nil
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Converts TEME to topocentric coordinates as seen from an observer.
|
|
99
|
+
#
|
|
100
|
+
# Both satellite (via equation of equinoxes) and observer (via
|
|
101
|
+
# R₃(GAST) * W) are placed in the TOD frame, then subtracted.
|
|
102
|
+
#
|
|
103
|
+
# @param observer [Astronoby::Observer] the observer
|
|
104
|
+
# @return [Astronoby::Topocentric] topocentric frame
|
|
105
|
+
def observed_by(observer)
|
|
106
|
+
satellite_position = Distance.vector_from_meters(
|
|
107
|
+
equation_of_equinoxes_matrix * @position.map(&:m)
|
|
108
|
+
)
|
|
109
|
+
satellite_velocity = Velocity.vector_from_mps(
|
|
110
|
+
equation_of_equinoxes_matrix * @velocity.map(&:mps)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
matrix = observer.earth_fixed_rotation_matrix_for(@instant)
|
|
114
|
+
observer_position = Distance.vector_from_meters(
|
|
115
|
+
matrix * observer.geocentric_position.map(&:m)
|
|
116
|
+
)
|
|
117
|
+
observer_velocity = Velocity.vector_from_mps(
|
|
118
|
+
matrix * observer.geocentric_velocity.map(&:mps)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
Topocentric.new(
|
|
122
|
+
position: satellite_position - observer_position,
|
|
123
|
+
velocity: satellite_velocity - observer_velocity,
|
|
124
|
+
instant: @instant,
|
|
125
|
+
center: Center.topocentric(observer),
|
|
126
|
+
target_body: nil,
|
|
127
|
+
observer: observer
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
def gcrs_rotation_matrix
|
|
134
|
+
@gcrs_rotation_matrix ||= begin
|
|
135
|
+
precession_matrix = Precession.matrix_for(@instant)
|
|
136
|
+
nutation_matrix = Nutation.matrix_for(@instant)
|
|
137
|
+
precession_matrix.transpose *
|
|
138
|
+
nutation_matrix.transpose *
|
|
139
|
+
equation_of_equinoxes_matrix
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def equation_of_equinoxes_matrix
|
|
144
|
+
@equation_of_equinoxes_matrix ||= begin
|
|
145
|
+
nutation = Nutation.new(instant: @instant)
|
|
146
|
+
dpsi = nutation.nutation_in_longitude
|
|
147
|
+
mean_obliquity = MeanObliquity.at(@instant)
|
|
148
|
+
eoe = dpsi.radians * mean_obliquity.cos
|
|
149
|
+
|
|
150
|
+
c, s = Math.cos(eoe), Math.sin(eoe)
|
|
151
|
+
Matrix[
|
|
152
|
+
[c, -s, 0],
|
|
153
|
+
[s, c, 0],
|
|
154
|
+
[0, 0, 1]
|
|
155
|
+
]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|