astronoby 0.8.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 +159 -0
- data/README.md +12 -5
- data/UPGRADING.md +109 -0
- data/docs/README.md +109 -16
- data/docs/angles.md +2 -1
- data/docs/configuration.md +20 -17
- data/docs/coordinates.md +73 -13
- data/docs/deep_sky_bodies.md +101 -0
- data/docs/ephem.md +6 -3
- data/docs/equinoxes_solstices_times.md +4 -3
- data/docs/glossary.md +97 -1
- data/docs/iers.md +40 -0
- data/docs/instant.md +21 -16
- data/docs/lunar_eclipses.md +93 -0
- data/docs/lunar_observation.md +87 -0
- data/docs/moon_phases.md +5 -2
- data/docs/observer.md +21 -7
- data/docs/planetary_phenomena.md +78 -0
- data/docs/reference_frames.md +193 -35
- data/docs/rise_transit_set_times.md +10 -8
- data/docs/{celestial_bodies.md → solar_system_bodies.md} +27 -5
- data/docs/twilight_times.md +25 -21
- data/lib/astronoby/angle.rb +69 -4
- data/lib/astronoby/angles/dms.rb +18 -1
- data/lib/astronoby/angles/hms.rb +14 -1
- data/lib/astronoby/angular_velocity.rb +97 -0
- data/lib/astronoby/bodies/deep_sky_object.rb +49 -0
- data/lib/astronoby/bodies/deep_sky_object_position.rb +142 -0
- data/lib/astronoby/bodies/earth.rb +9 -42
- 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 +162 -15
- data/lib/astronoby/bodies/neptune.rb +10 -0
- data/lib/astronoby/bodies/saturn.rb +10 -0
- data/lib/astronoby/bodies/solar_system_body.rb +257 -53
- 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 +1 -0
- data/lib/astronoby/center.rb +84 -0
- data/lib/astronoby/constants.rb +7 -2
- data/lib/astronoby/constellation.rb +9 -1
- data/lib/astronoby/coordinates/ecliptic.rb +10 -1
- data/lib/astronoby/coordinates/equatorial.rb +66 -13
- data/lib/astronoby/coordinates/geodetic.rb +102 -0
- data/lib/astronoby/coordinates/horizontal.rb +13 -1
- data/lib/astronoby/distance.rb +41 -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 +80 -0
- data/lib/astronoby/events/extremum_event.rb +15 -0
- 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_calculator.rb +9 -6
- 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 +1 -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 +35 -9
- 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 +25 -16
- 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 +31 -5
- 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/stellar_propagation.rb +162 -0
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +31 -0
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +101 -0
- data/lib/astronoby/time/greenwich_sidereal_time.rb +41 -58
- data/lib/astronoby/time/local_apparent_sidereal_time.rb +63 -0
- data/lib/astronoby/time/local_mean_sidereal_time.rb +63 -0
- data/lib/astronoby/time/local_sidereal_time.rb +59 -26
- data/lib/astronoby/time/sidereal_time.rb +64 -0
- data/lib/astronoby/true_obliquity.rb +4 -0
- data/lib/astronoby/util/maths.rb +8 -0
- data/lib/astronoby/util/time.rb +10 -467
- data/lib/astronoby/vector.rb +10 -0
- data/lib/astronoby/velocity.rb +44 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +33 -0
- metadata +58 -6
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents Mercury.
|
|
4
5
|
class Mercury < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(2_439_700)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -0.613
|
|
8
|
+
ORBITAL_PERIOD = 87.969
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Mercury is an inferior planet
|
|
11
|
+
def self.inferior_planet?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
16
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
17
|
def self.ephemeris_segments(_ephem_source)
|
|
9
18
|
[[SOLAR_SYSTEM_BARYCENTER, MERCURY_BARYCENTER]]
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @return [Float] absolute magnitude
|
|
12
22
|
def self.absolute_magnitude
|
|
13
23
|
ABSOLUTE_MAGNITUDE
|
|
14
24
|
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents the Moon. Provides phase events, apoapsis/periapsis events,
|
|
5
|
+
# and phase fraction.
|
|
4
6
|
class Moon < SolarSystemBody
|
|
5
7
|
SEMIDIAMETER_VARIATION = 0.7275
|
|
6
8
|
EQUATORIAL_RADIUS = Distance.from_meters(1_737_400)
|
|
7
9
|
ABSOLUTE_MAGNITUDE = 0.28
|
|
10
|
+
ORBITAL_PERIOD = 27.504339
|
|
8
11
|
|
|
12
|
+
# @param ephem_source [Symbol] the ephemeris source type
|
|
13
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
9
14
|
def self.ephemeris_segments(ephem_source)
|
|
10
15
|
if ephem_source == ::Ephem::SPK::JPL_DE
|
|
11
16
|
[
|
|
@@ -33,17 +38,155 @@ module Astronoby
|
|
|
33
38
|
Events::MoonPhases.phases_for(year: year, month: month)
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
# @return [Float] absolute magnitude
|
|
36
42
|
def self.absolute_magnitude
|
|
37
43
|
ABSOLUTE_MAGNITUDE
|
|
38
44
|
end
|
|
39
45
|
|
|
46
|
+
# Finds all apoapsis events between two times
|
|
47
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
48
|
+
# @param start_time [Time] Start time
|
|
49
|
+
# @param end_time [Time] End time
|
|
50
|
+
# @return [Array<Astronoby::ExtremumEvent>] Array of apoapsis events
|
|
51
|
+
def self.apoapsis_events(
|
|
52
|
+
ephem:,
|
|
53
|
+
start_time:,
|
|
54
|
+
end_time:,
|
|
55
|
+
samples_per_period: 60
|
|
56
|
+
)
|
|
57
|
+
ExtremumCalculator.new(
|
|
58
|
+
body: self,
|
|
59
|
+
primary_body: Earth,
|
|
60
|
+
ephem: ephem,
|
|
61
|
+
samples_per_period: samples_per_period
|
|
62
|
+
).apoapsis_events_between(start_time, end_time)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Finds all periapsis events between two times
|
|
66
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
67
|
+
# @param start_time [Time] Start time
|
|
68
|
+
# @param end_time [Time] End time
|
|
69
|
+
# @return [Array<Astronoby::ExtremumEvent>] Array of periapsis events
|
|
70
|
+
def self.periapsis_events(
|
|
71
|
+
ephem:,
|
|
72
|
+
start_time:,
|
|
73
|
+
end_time:,
|
|
74
|
+
samples_per_period: 60
|
|
75
|
+
)
|
|
76
|
+
ExtremumCalculator.new(
|
|
77
|
+
body: self,
|
|
78
|
+
primary_body: Earth,
|
|
79
|
+
ephem: ephem,
|
|
80
|
+
samples_per_period: samples_per_period
|
|
81
|
+
).periapsis_events_between(start_time, end_time)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Finds all lunar eclipses whose greatest instant falls between two times
|
|
85
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
86
|
+
# @param start_time [Time] Start time
|
|
87
|
+
# @param end_time [Time] End time
|
|
88
|
+
# @return [Array<Astronoby::LunarEclipse>] Lunar eclipses in the range
|
|
89
|
+
def self.eclipse_events(ephem:, start_time:, end_time:)
|
|
90
|
+
LunarEclipseCalculator
|
|
91
|
+
.new(ephem: ephem)
|
|
92
|
+
.events_between(start_time, end_time)
|
|
93
|
+
end
|
|
94
|
+
|
|
40
95
|
# @return [Float] Phase fraction, from 0 to 1
|
|
41
96
|
def current_phase_fraction
|
|
42
97
|
mean_elongation.degrees / Constants::DEGREES_PER_CIRCLE
|
|
43
98
|
end
|
|
44
99
|
|
|
100
|
+
# Total geocentric libration of the Moon, in longitude and latitude.
|
|
101
|
+
#
|
|
102
|
+
# With an orientation kernel (see +orientation:+ on the constructor), this
|
|
103
|
+
# is the sub-Earth point from the integrated DE orientation, accurate to
|
|
104
|
+
# better than an arcsecond. Without one, it is the analytic optical plus
|
|
105
|
+
# physical libration (Meeus, Astronomical Algorithms, 2nd ed., chapter 53).
|
|
106
|
+
#
|
|
107
|
+
# @return [Astronoby::Libration] Libration in longitude and latitude
|
|
108
|
+
def libration
|
|
109
|
+
lunar_ephemeris.libration
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Position angle of the Moon's axis of rotation, measured eastward from the
|
|
113
|
+
# north point of the disk.
|
|
114
|
+
#
|
|
115
|
+
# With an orientation kernel this comes from the integrated DE orientation;
|
|
116
|
+
# without one, from the analytic series (Meeus, Astronomical Algorithms,
|
|
117
|
+
# 2nd ed., chapter 53).
|
|
118
|
+
#
|
|
119
|
+
# @return [Astronoby::Angle] Position angle of the axis
|
|
120
|
+
def position_angle_of_axis
|
|
121
|
+
lunar_ephemeris.position_angle_of_axis
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Position angle of the Moon's bright limb: the position angle of the
|
|
125
|
+
# midpoint of the illuminated limb, measured eastward from the north point
|
|
126
|
+
# of the disk. Geocentric, from the apparent equatorial coordinates of the
|
|
127
|
+
# Moon and the Sun.
|
|
128
|
+
#
|
|
129
|
+
# Source:
|
|
130
|
+
# Title: Astronomical Algorithms
|
|
131
|
+
# Author: Jean Meeus
|
|
132
|
+
# Edition: 2nd edition
|
|
133
|
+
# Chapter: 48 - Illuminated Fraction of the Moon's Disk
|
|
134
|
+
# @return [Astronoby::Angle, nil] Position angle of the bright limb, between
|
|
135
|
+
# 0 and 360 degrees
|
|
136
|
+
def bright_limb_position_angle
|
|
137
|
+
return unless sun
|
|
138
|
+
|
|
139
|
+
@bright_limb_position_angle ||= begin
|
|
140
|
+
angle = apparent.equatorial.position_angle_to(sun.apparent.equatorial)
|
|
141
|
+
Angle.from_degrees(angle.degrees % Constants::DEGREES_PER_CIRCLE)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Parallactic angle of the Moon for a given observer: the angle at the Moon
|
|
146
|
+
# between the direction of the north celestial pole and the direction of
|
|
147
|
+
# the observer's zenith. Computed from the topocentric place, where lunar
|
|
148
|
+
# parallax is significant.
|
|
149
|
+
#
|
|
150
|
+
# Source:
|
|
151
|
+
# Title: Astronomical Algorithms
|
|
152
|
+
# Author: Jean Meeus
|
|
153
|
+
# Edition: 2nd edition
|
|
154
|
+
# Chapter: 14 - The Parallactic Angle
|
|
155
|
+
# @param observer [Astronoby::Observer] Observer for whom to compute the
|
|
156
|
+
# parallactic angle
|
|
157
|
+
# @return [Astronoby::Angle] Parallactic angle
|
|
158
|
+
def parallactic_angle(observer:)
|
|
159
|
+
equatorial = observed_by(observer).equatorial
|
|
160
|
+
declination = equatorial.declination
|
|
161
|
+
hour_angle = equatorial.compute_hour_angle(
|
|
162
|
+
time: @instant.to_time,
|
|
163
|
+
longitude: observer.longitude
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
Angle.from_radians(
|
|
167
|
+
Math.atan2(
|
|
168
|
+
hour_angle.sin,
|
|
169
|
+
observer.latitude.tan * declination.cos -
|
|
170
|
+
declination.sin * hour_angle.cos
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
45
175
|
private
|
|
46
176
|
|
|
177
|
+
def lunar_ephemeris
|
|
178
|
+
@lunar_ephemeris ||=
|
|
179
|
+
if orientation
|
|
180
|
+
MoonOrientationEphemeris.new(self)
|
|
181
|
+
else
|
|
182
|
+
MoonPhysicalEphemeris.new(self)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def primary_body_geometric
|
|
187
|
+
earth_geometric
|
|
188
|
+
end
|
|
189
|
+
|
|
47
190
|
# Source:
|
|
48
191
|
# Title: Astronomical Algorithms
|
|
49
192
|
# Author: Jean Meeus
|
|
@@ -65,29 +208,33 @@ module Astronoby
|
|
|
65
208
|
(@instant.tt - JulianDate::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
|
|
66
209
|
end
|
|
67
210
|
|
|
68
|
-
private
|
|
69
|
-
|
|
70
211
|
# Source:
|
|
71
212
|
# Title: Computing Apparent Planetary Magnitudes for The Astronomical
|
|
72
213
|
# Almanac (2018)
|
|
73
214
|
# Authors: Anthony Mallama and James L. Hilton
|
|
74
215
|
def magnitude_correction_term
|
|
75
216
|
phase_angle_degrees = phase_angle.degrees
|
|
76
|
-
if phase_angle_degrees <= 150 && current_phase_fraction
|
|
77
|
-
|
|
78
|
-
1.6057 * 10**-4 * phase_angle_degrees**2 +
|
|
79
|
-
3.1543 * 10**-6 * phase_angle_degrees**3 -
|
|
80
|
-
2.0667 * 10**-8 * phase_angle_degrees**4 +
|
|
81
|
-
6.2553 * 10**-11 * phase_angle_degrees**5
|
|
82
|
-
elsif phase_angle_degrees <= 150 && current_phase_fraction > 0.5
|
|
83
|
-
3.3234 * 10**-2 * phase_angle_degrees -
|
|
84
|
-
3.0725 * 10**-4 * phase_angle_degrees**2 +
|
|
85
|
-
6.1575 * 10**-6 * phase_angle_degrees**3 -
|
|
86
|
-
4.7723 * 10**-8 * phase_angle_degrees**4 +
|
|
87
|
-
1.4681 * 10**-10 * phase_angle_degrees**5
|
|
217
|
+
if phase_angle_degrees <= 150 && current_phase_fraction > 0.5
|
|
218
|
+
waning_magnitude_correction(phase_angle_degrees)
|
|
88
219
|
else
|
|
89
|
-
|
|
220
|
+
waxing_magnitude_correction(phase_angle_degrees)
|
|
90
221
|
end
|
|
91
222
|
end
|
|
223
|
+
|
|
224
|
+
def waxing_magnitude_correction(phase_angle_degrees)
|
|
225
|
+
2.9994 * 10**-2 * phase_angle_degrees -
|
|
226
|
+
1.6057 * 10**-4 * phase_angle_degrees**2 +
|
|
227
|
+
3.1543 * 10**-6 * phase_angle_degrees**3 -
|
|
228
|
+
2.0667 * 10**-8 * phase_angle_degrees**4 +
|
|
229
|
+
6.2553 * 10**-11 * phase_angle_degrees**5
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def waning_magnitude_correction(phase_angle_degrees)
|
|
233
|
+
3.3234 * 10**-2 * phase_angle_degrees -
|
|
234
|
+
3.0725 * 10**-4 * phase_angle_degrees**2 +
|
|
235
|
+
6.1575 * 10**-6 * phase_angle_degrees**3 -
|
|
236
|
+
4.7723 * 10**-8 * phase_angle_degrees**4 +
|
|
237
|
+
1.4681 * 10**-10 * phase_angle_degrees**5
|
|
238
|
+
end
|
|
92
239
|
end
|
|
93
240
|
end
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents Neptune.
|
|
4
5
|
class Neptune < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(24_764_000)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -7.0
|
|
8
|
+
ORBITAL_PERIOD = 60182.0
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Neptune is a superior planet
|
|
11
|
+
def self.superior_planet?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
16
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
17
|
def self.ephemeris_segments(_ephem_source)
|
|
9
18
|
[[SOLAR_SYSTEM_BARYCENTER, NEPTUNE_BARYCENTER]]
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @return [Float] absolute magnitude
|
|
12
22
|
def self.absolute_magnitude
|
|
13
23
|
ABSOLUTE_MAGNITUDE
|
|
14
24
|
end
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents Saturn.
|
|
4
5
|
class Saturn < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(60_268_000)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -8.914
|
|
8
|
+
ORBITAL_PERIOD = 10759.22
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Saturn is a superior planet
|
|
11
|
+
def self.superior_planet?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
16
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
17
|
def self.ephemeris_segments(_ephem_source)
|
|
9
18
|
[[SOLAR_SYSTEM_BARYCENTER, SATURN_BARYCENTER]]
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @return [Float] absolute magnitude
|
|
12
22
|
def self.absolute_magnitude
|
|
13
23
|
ABSOLUTE_MAGNITUDE
|
|
14
24
|
end
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Base class for solar system bodies. Provides the reference frame chain
|
|
5
|
+
# (geometric -> astrometric -> mean-of-date -> apparent -> topocentric)
|
|
6
|
+
# and common observational properties (phase angle, magnitude, etc.).
|
|
4
7
|
class SolarSystemBody
|
|
8
|
+
include Position
|
|
9
|
+
extend Body
|
|
10
|
+
|
|
5
11
|
SOLAR_SYSTEM_BARYCENTER = 0
|
|
6
12
|
SUN = 10
|
|
7
13
|
MERCURY_BARYCENTER = 1
|
|
@@ -17,12 +23,37 @@ module Astronoby
|
|
|
17
23
|
URANUS_BARYCENTER = 7
|
|
18
24
|
NEPTUNE_BARYCENTER = 8
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
# @return [Astronoby::Instant] the time instant
|
|
27
|
+
attr_reader :instant
|
|
28
|
+
|
|
29
|
+
# @return [::Ephem::SPK] the ephemeris data source
|
|
30
|
+
attr_reader :ephem
|
|
31
|
+
|
|
32
|
+
# @return [Astronoby::Orientation, nil] the orientation kernel, if provided
|
|
33
|
+
attr_reader :orientation
|
|
34
|
+
|
|
35
|
+
# Creates a new body instance at the given instant.
|
|
36
|
+
#
|
|
37
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
38
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
39
|
+
# @param orientation [Astronoby::Orientation, nil] orientation kernel
|
|
40
|
+
# @return [Astronoby::SolarSystemBody] a new body instance
|
|
41
|
+
def self.at(instant, ephem:, orientation: nil)
|
|
42
|
+
new(ephem: ephem, instant: instant, orientation: orientation)
|
|
43
|
+
end
|
|
21
44
|
|
|
45
|
+
# Computes the geometric reference frame for this body.
|
|
46
|
+
#
|
|
47
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
48
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
49
|
+
# @return [Astronoby::Geometric] the geometric frame
|
|
22
50
|
def self.geometric(ephem:, instant:)
|
|
23
51
|
compute_geometric(ephem: ephem, instant: instant)
|
|
24
52
|
end
|
|
25
53
|
|
|
54
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
55
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
56
|
+
# @return [Astronoby::Geometric] the geometric frame
|
|
26
57
|
def self.compute_geometric(ephem:, instant:)
|
|
27
58
|
segments = ephemeris_segments(ephem.type)
|
|
28
59
|
segment1 = segments[0]
|
|
@@ -62,63 +93,196 @@ module Astronoby
|
|
|
62
93
|
end
|
|
63
94
|
end
|
|
64
95
|
|
|
96
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
97
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
98
|
+
# @raise [NotImplementedError] must be implemented by subclasses
|
|
65
99
|
def self.ephemeris_segments(_ephem_source)
|
|
66
100
|
raise NotImplementedError
|
|
67
101
|
end
|
|
68
102
|
|
|
103
|
+
# @return [Float, nil] absolute magnitude of the body
|
|
69
104
|
def self.absolute_magnitude
|
|
70
105
|
nil
|
|
71
106
|
end
|
|
72
107
|
|
|
73
|
-
|
|
108
|
+
# @return [Boolean] true for an inferior planet (Mercury, Venus)
|
|
109
|
+
def self.inferior_planet?
|
|
110
|
+
false
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [Boolean] true for a superior planet (Mars through Neptune)
|
|
114
|
+
def self.superior_planet?
|
|
115
|
+
false
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @return [Boolean] true for a planet (excludes the Sun, Earth and Moon)
|
|
119
|
+
def self.planet?
|
|
120
|
+
inferior_planet? || superior_planet?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @param observer [Astronoby::Observer] Observer for whom to calculate rise,
|
|
124
|
+
# transit, and set events
|
|
125
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
126
|
+
# @param date [Date] Date for which to calculate rise, transit, and set
|
|
127
|
+
# events (optional)
|
|
128
|
+
# @param start_time [Time] Start time for rise, transit, and set event
|
|
129
|
+
# calculation (optional)
|
|
130
|
+
# @param end_time [Time] End time for rise, transit, and set event
|
|
131
|
+
# calculation (optional)
|
|
132
|
+
# @param utc_offset [String] UTC offset for the given date (e.g., "+02:00")
|
|
133
|
+
# @return [Astronoby::RiseTransitSetEvent,
|
|
134
|
+
# Array<Astronoby::RiseTransitSetEvent>] Rise, transit, and set events for
|
|
135
|
+
# the given date or time range.
|
|
136
|
+
def self.rise_transit_set_events(
|
|
137
|
+
observer:,
|
|
138
|
+
ephem:,
|
|
139
|
+
date: nil,
|
|
140
|
+
start_time: nil,
|
|
141
|
+
end_time: nil,
|
|
142
|
+
utc_offset: 0
|
|
143
|
+
)
|
|
144
|
+
calculator = RiseTransitSetCalculator.new(
|
|
145
|
+
body: self,
|
|
146
|
+
observer: observer,
|
|
147
|
+
ephem: ephem
|
|
148
|
+
)
|
|
149
|
+
if date
|
|
150
|
+
calculator.events_on(date, utc_offset: utc_offset)
|
|
151
|
+
else
|
|
152
|
+
calculator.events_between(start_time, end_time)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
157
|
+
# @param start_time [Time] start time
|
|
158
|
+
# @param end_time [Time] end time
|
|
159
|
+
# @param samples_per_period [Integer] number of samples per synodic period
|
|
160
|
+
# @return [Array<Astronoby::Conjunction>] conjunctions with the Sun
|
|
161
|
+
# @raise [Astronoby::UnsupportedEventError] unless the body is a planet
|
|
162
|
+
def self.conjunction_events(
|
|
163
|
+
ephem:,
|
|
164
|
+
start_time:,
|
|
165
|
+
end_time:,
|
|
166
|
+
samples_per_period: 60
|
|
167
|
+
)
|
|
168
|
+
unless planet?
|
|
169
|
+
raise UnsupportedEventError, "#{self} has no conjunctions with the Sun"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
ConjunctionOppositionCalculator.new(
|
|
173
|
+
body: self,
|
|
174
|
+
ephem: ephem,
|
|
175
|
+
samples_per_period: samples_per_period
|
|
176
|
+
).conjunction_events_between(start_time, end_time)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
180
|
+
# @param start_time [Time] start time
|
|
181
|
+
# @param end_time [Time] end time
|
|
182
|
+
# @param samples_per_period [Integer] number of samples per synodic period
|
|
183
|
+
# @return [Array<Astronoby::Opposition>] oppositions with the Sun
|
|
184
|
+
# @raise [Astronoby::UnsupportedEventError] unless the body is a superior
|
|
185
|
+
# planet
|
|
186
|
+
def self.opposition_events(
|
|
187
|
+
ephem:,
|
|
188
|
+
start_time:,
|
|
189
|
+
end_time:,
|
|
190
|
+
samples_per_period: 60
|
|
191
|
+
)
|
|
192
|
+
unless superior_planet?
|
|
193
|
+
raise UnsupportedEventError, "#{self} has no oppositions with the Sun"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
ConjunctionOppositionCalculator.new(
|
|
197
|
+
body: self,
|
|
198
|
+
ephem: ephem,
|
|
199
|
+
samples_per_period: samples_per_period
|
|
200
|
+
).opposition_events_between(start_time, end_time)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
204
|
+
# @param start_time [Time] start time
|
|
205
|
+
# @param end_time [Time] end time
|
|
206
|
+
# @param samples_per_period [Integer] number of samples per synodic period
|
|
207
|
+
# @return [Array<Astronoby::GreatestElongation>] greatest elongations
|
|
208
|
+
# @raise [Astronoby::UnsupportedEventError] unless the body is an inferior
|
|
209
|
+
# planet
|
|
210
|
+
def self.greatest_elongation_events(
|
|
211
|
+
ephem:,
|
|
212
|
+
start_time:,
|
|
213
|
+
end_time:,
|
|
214
|
+
samples_per_period: 60
|
|
215
|
+
)
|
|
216
|
+
unless inferior_planet?
|
|
217
|
+
raise UnsupportedEventError,
|
|
218
|
+
"#{self} has no greatest elongations from the Sun"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
GreatestElongationCalculator.new(
|
|
222
|
+
body: self,
|
|
223
|
+
ephem: ephem,
|
|
224
|
+
samples_per_period: samples_per_period
|
|
225
|
+
).greatest_elongation_events_between(start_time, end_time)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
229
|
+
# @param instant [Astronoby::Instant] Instant for which to calculate the
|
|
230
|
+
# phase angle
|
|
231
|
+
# @param orientation [Astronoby::Orientation, nil] Orientation kernel,
|
|
232
|
+
# enabling arcsecond-accurate lunar libration and axis position angle
|
|
233
|
+
def initialize(ephem:, instant:, orientation: nil)
|
|
234
|
+
@ephem = ephem
|
|
74
235
|
@instant = instant
|
|
75
|
-
@
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
compute_sun(ephem) if requires_sun_data?
|
|
236
|
+
@orientation = orientation
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @return [Astronoby::Geometric] the geometric reference frame (BCRS)
|
|
240
|
+
def geometric
|
|
241
|
+
@geometric ||= self.class.compute_geometric(
|
|
242
|
+
ephem: @ephem,
|
|
243
|
+
instant: @instant
|
|
244
|
+
)
|
|
85
245
|
end
|
|
86
246
|
|
|
247
|
+
# @return [Astronoby::Geometric] Earth's geometric reference frame
|
|
248
|
+
def earth_geometric
|
|
249
|
+
@earth_geometric ||= Earth.geometric(ephem: @ephem, instant: @instant)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @return [Astronoby::Astrometric] the astrometric reference frame (GCRS)
|
|
87
253
|
def astrometric
|
|
88
254
|
@astrometric ||= Astrometric.build_from_geometric(
|
|
89
255
|
instant: @instant,
|
|
90
|
-
earth_geometric:
|
|
91
|
-
light_time_corrected_position:
|
|
92
|
-
light_time_corrected_velocity:
|
|
93
|
-
target_body:
|
|
256
|
+
earth_geometric: earth_geometric,
|
|
257
|
+
light_time_corrected_position: light_time_corrected_position,
|
|
258
|
+
light_time_corrected_velocity: light_time_corrected_velocity,
|
|
259
|
+
target_body: body
|
|
94
260
|
)
|
|
95
261
|
end
|
|
96
262
|
|
|
263
|
+
# @return [Astronoby::MeanOfDate] the mean-of-date reference frame
|
|
97
264
|
def mean_of_date
|
|
98
265
|
@mean_of_date ||= MeanOfDate.build_from_geometric(
|
|
99
266
|
instant: @instant,
|
|
100
|
-
target_geometric:
|
|
101
|
-
earth_geometric:
|
|
102
|
-
target_body:
|
|
267
|
+
target_geometric: geometric,
|
|
268
|
+
earth_geometric: earth_geometric,
|
|
269
|
+
target_body: body
|
|
103
270
|
)
|
|
104
271
|
end
|
|
105
272
|
|
|
273
|
+
# @return [Astronoby::Apparent] the apparent reference frame
|
|
106
274
|
def apparent
|
|
107
275
|
@apparent ||= Apparent.build_from_astrometric(
|
|
108
276
|
instant: @instant,
|
|
109
277
|
target_astrometric: astrometric,
|
|
110
|
-
earth_geometric:
|
|
111
|
-
target_body:
|
|
278
|
+
earth_geometric: earth_geometric,
|
|
279
|
+
target_body: body
|
|
112
280
|
)
|
|
113
281
|
end
|
|
114
282
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
observer: observer,
|
|
119
|
-
instant: @instant,
|
|
120
|
-
target_body: self
|
|
121
|
-
)
|
|
283
|
+
# @return [Astronoby::Body] the body definition (the class itself)
|
|
284
|
+
def body
|
|
285
|
+
self.class
|
|
122
286
|
end
|
|
123
287
|
|
|
124
288
|
# Returns the constellation of the body
|
|
@@ -132,6 +296,25 @@ module Astronoby
|
|
|
132
296
|
)
|
|
133
297
|
end
|
|
134
298
|
|
|
299
|
+
# Apparent geocentric Sun-Earth-body angle
|
|
300
|
+
# @return [Astronoby::Angle, nil] Elongation of the body
|
|
301
|
+
def elongation
|
|
302
|
+
return unless sun
|
|
303
|
+
|
|
304
|
+
@elongation ||= sun.apparent.separation_from(apparent)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# @return [Boolean] true when the body is east of the Sun
|
|
308
|
+
def eastern?
|
|
309
|
+
(apparent.ecliptic.longitude - sun.apparent.ecliptic.longitude)
|
|
310
|
+
.sin.positive?
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# @return [Boolean] true when the body is west of the Sun
|
|
314
|
+
def western?
|
|
315
|
+
!eastern?
|
|
316
|
+
end
|
|
317
|
+
|
|
135
318
|
# Source:
|
|
136
319
|
# Title: Astronomical Algorithms
|
|
137
320
|
# Author: Jean Meeus
|
|
@@ -139,23 +322,12 @@ module Astronoby
|
|
|
139
322
|
# Chapter: 48 - Illuminated Fraction of the Moon's Disk
|
|
140
323
|
# @return [Astronoby::Angle, nil] Phase angle of the body
|
|
141
324
|
def phase_angle
|
|
142
|
-
return unless
|
|
325
|
+
return unless sun
|
|
143
326
|
|
|
144
327
|
@phase_angle ||= begin
|
|
145
|
-
|
|
146
|
-
@sun.apparent.equatorial.declination.sin *
|
|
147
|
-
apparent.equatorial.declination.sin +
|
|
148
|
-
@sun.apparent.equatorial.declination.cos *
|
|
149
|
-
apparent.equatorial.declination.cos *
|
|
150
|
-
(
|
|
151
|
-
@sun.apparent.equatorial.right_ascension -
|
|
152
|
-
apparent.equatorial.right_ascension
|
|
153
|
-
).cos
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
term1 = @sun.astrometric.distance.km * geocentric_elongation.sin
|
|
328
|
+
term1 = sun.astrometric.distance.km * elongation.sin
|
|
157
329
|
term2 = astrometric.distance.km -
|
|
158
|
-
|
|
330
|
+
sun.astrometric.distance.km * elongation.cos
|
|
159
331
|
angle = Angle.atan(term1 / term2)
|
|
160
332
|
Astronoby::Util::Trigonometry
|
|
161
333
|
.adjustement_for_arctangent(term1, term2, angle)
|
|
@@ -182,7 +354,7 @@ module Astronoby
|
|
|
182
354
|
|
|
183
355
|
@apparent_magnitude ||= begin
|
|
184
356
|
body_sun_distance =
|
|
185
|
-
(astrometric.position -
|
|
357
|
+
(astrometric.position - sun.astrometric.position).magnitude
|
|
186
358
|
self.class.absolute_magnitude +
|
|
187
359
|
5 * Math.log10(body_sun_distance.au * astrometric.distance.au) +
|
|
188
360
|
magnitude_correction_term
|
|
@@ -202,22 +374,54 @@ module Astronoby
|
|
|
202
374
|
end
|
|
203
375
|
end
|
|
204
376
|
|
|
377
|
+
# @return [Boolean] True if the body is approaching its primary
|
|
378
|
+
# body, false otherwise.
|
|
379
|
+
def approaching_primary?
|
|
380
|
+
relative_position =
|
|
381
|
+
(geometric.position - primary_body_geometric.position).map(&:m)
|
|
382
|
+
relative_velocity =
|
|
383
|
+
(geometric.velocity - primary_body_geometric.velocity).map(&:mps)
|
|
384
|
+
radial_velocity_component = Astronoby::Util::Maths
|
|
385
|
+
.dot_product(relative_position, relative_velocity)
|
|
386
|
+
distance = Math.sqrt(
|
|
387
|
+
Astronoby::Util::Maths.dot_product(relative_position, relative_position)
|
|
388
|
+
)
|
|
389
|
+
radial_velocity_component / distance < 0
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
# @return [Boolean] True if the body is receding from its primary
|
|
393
|
+
# body, false otherwise.
|
|
394
|
+
def receding_from_primary?
|
|
395
|
+
!approaching_primary?
|
|
396
|
+
end
|
|
397
|
+
|
|
205
398
|
private
|
|
206
399
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def
|
|
212
|
-
|
|
400
|
+
def sun
|
|
401
|
+
@sun ||= Sun.new(instant: @instant, ephem: @ephem)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def primary_body_geometric
|
|
405
|
+
sun.geometric
|
|
213
406
|
end
|
|
214
407
|
|
|
215
|
-
def
|
|
216
|
-
|
|
408
|
+
def light_time_corrected_position
|
|
409
|
+
compute_light_time_correction unless @light_time_corrected_position
|
|
410
|
+
@light_time_corrected_position
|
|
217
411
|
end
|
|
218
412
|
|
|
219
|
-
def
|
|
220
|
-
|
|
413
|
+
def light_time_corrected_velocity
|
|
414
|
+
compute_light_time_correction unless @light_time_corrected_velocity
|
|
415
|
+
@light_time_corrected_velocity
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def compute_light_time_correction
|
|
419
|
+
@light_time_corrected_position, @light_time_corrected_velocity =
|
|
420
|
+
Correction::LightTimeDelay.compute(
|
|
421
|
+
center: earth_geometric,
|
|
422
|
+
target: geometric,
|
|
423
|
+
ephem: @ephem
|
|
424
|
+
)
|
|
221
425
|
end
|
|
222
426
|
|
|
223
427
|
# Source:
|