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
|
@@ -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,16 +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
|
|
21
31
|
|
|
22
|
-
|
|
23
|
-
|
|
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)
|
|
24
43
|
end
|
|
25
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
|
|
26
50
|
def self.geometric(ephem:, instant:)
|
|
27
51
|
compute_geometric(ephem: ephem, instant: instant)
|
|
28
52
|
end
|
|
29
53
|
|
|
54
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
55
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
56
|
+
# @return [Astronoby::Geometric] the geometric frame
|
|
30
57
|
def self.compute_geometric(ephem:, instant:)
|
|
31
58
|
segments = ephemeris_segments(ephem.type)
|
|
32
59
|
segment1 = segments[0]
|
|
@@ -66,63 +93,196 @@ module Astronoby
|
|
|
66
93
|
end
|
|
67
94
|
end
|
|
68
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
|
|
69
99
|
def self.ephemeris_segments(_ephem_source)
|
|
70
100
|
raise NotImplementedError
|
|
71
101
|
end
|
|
72
102
|
|
|
103
|
+
# @return [Float, nil] absolute magnitude of the body
|
|
73
104
|
def self.absolute_magnitude
|
|
74
105
|
nil
|
|
75
106
|
end
|
|
76
107
|
|
|
77
|
-
|
|
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
|
|
78
235
|
@instant = instant
|
|
79
|
-
@
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# @return [Astronoby::Geometric] Earth's geometric reference frame
|
|
248
|
+
def earth_geometric
|
|
249
|
+
@earth_geometric ||= Earth.geometric(ephem: @ephem, instant: @instant)
|
|
89
250
|
end
|
|
90
251
|
|
|
252
|
+
# @return [Astronoby::Astrometric] the astrometric reference frame (GCRS)
|
|
91
253
|
def astrometric
|
|
92
254
|
@astrometric ||= Astrometric.build_from_geometric(
|
|
93
255
|
instant: @instant,
|
|
94
|
-
earth_geometric:
|
|
95
|
-
light_time_corrected_position:
|
|
96
|
-
light_time_corrected_velocity:
|
|
97
|
-
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
|
|
98
260
|
)
|
|
99
261
|
end
|
|
100
262
|
|
|
263
|
+
# @return [Astronoby::MeanOfDate] the mean-of-date reference frame
|
|
101
264
|
def mean_of_date
|
|
102
265
|
@mean_of_date ||= MeanOfDate.build_from_geometric(
|
|
103
266
|
instant: @instant,
|
|
104
|
-
target_geometric:
|
|
105
|
-
earth_geometric:
|
|
106
|
-
target_body:
|
|
267
|
+
target_geometric: geometric,
|
|
268
|
+
earth_geometric: earth_geometric,
|
|
269
|
+
target_body: body
|
|
107
270
|
)
|
|
108
271
|
end
|
|
109
272
|
|
|
273
|
+
# @return [Astronoby::Apparent] the apparent reference frame
|
|
110
274
|
def apparent
|
|
111
275
|
@apparent ||= Apparent.build_from_astrometric(
|
|
112
276
|
instant: @instant,
|
|
113
277
|
target_astrometric: astrometric,
|
|
114
|
-
earth_geometric:
|
|
115
|
-
target_body:
|
|
278
|
+
earth_geometric: earth_geometric,
|
|
279
|
+
target_body: body
|
|
116
280
|
)
|
|
117
281
|
end
|
|
118
282
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
observer: observer,
|
|
123
|
-
instant: @instant,
|
|
124
|
-
target_body: self
|
|
125
|
-
)
|
|
283
|
+
# @return [Astronoby::Body] the body definition (the class itself)
|
|
284
|
+
def body
|
|
285
|
+
self.class
|
|
126
286
|
end
|
|
127
287
|
|
|
128
288
|
# Returns the constellation of the body
|
|
@@ -136,6 +296,25 @@ module Astronoby
|
|
|
136
296
|
)
|
|
137
297
|
end
|
|
138
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
|
+
|
|
139
318
|
# Source:
|
|
140
319
|
# Title: Astronomical Algorithms
|
|
141
320
|
# Author: Jean Meeus
|
|
@@ -143,23 +322,12 @@ module Astronoby
|
|
|
143
322
|
# Chapter: 48 - Illuminated Fraction of the Moon's Disk
|
|
144
323
|
# @return [Astronoby::Angle, nil] Phase angle of the body
|
|
145
324
|
def phase_angle
|
|
146
|
-
return unless
|
|
325
|
+
return unless sun
|
|
147
326
|
|
|
148
327
|
@phase_angle ||= begin
|
|
149
|
-
|
|
150
|
-
@sun.apparent.equatorial.declination.sin *
|
|
151
|
-
apparent.equatorial.declination.sin +
|
|
152
|
-
@sun.apparent.equatorial.declination.cos *
|
|
153
|
-
apparent.equatorial.declination.cos *
|
|
154
|
-
(
|
|
155
|
-
@sun.apparent.equatorial.right_ascension -
|
|
156
|
-
apparent.equatorial.right_ascension
|
|
157
|
-
).cos
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
term1 = @sun.astrometric.distance.km * geocentric_elongation.sin
|
|
328
|
+
term1 = sun.astrometric.distance.km * elongation.sin
|
|
161
329
|
term2 = astrometric.distance.km -
|
|
162
|
-
|
|
330
|
+
sun.astrometric.distance.km * elongation.cos
|
|
163
331
|
angle = Angle.atan(term1 / term2)
|
|
164
332
|
Astronoby::Util::Trigonometry
|
|
165
333
|
.adjustement_for_arctangent(term1, term2, angle)
|
|
@@ -186,7 +354,7 @@ module Astronoby
|
|
|
186
354
|
|
|
187
355
|
@apparent_magnitude ||= begin
|
|
188
356
|
body_sun_distance =
|
|
189
|
-
(astrometric.position -
|
|
357
|
+
(astrometric.position - sun.astrometric.position).magnitude
|
|
190
358
|
self.class.absolute_magnitude +
|
|
191
359
|
5 * Math.log10(body_sun_distance.au * astrometric.distance.au) +
|
|
192
360
|
magnitude_correction_term
|
|
@@ -206,13 +374,13 @@ module Astronoby
|
|
|
206
374
|
end
|
|
207
375
|
end
|
|
208
376
|
|
|
209
|
-
# @return [Boolean] True if the body is approaching
|
|
210
|
-
# body
|
|
377
|
+
# @return [Boolean] True if the body is approaching its primary
|
|
378
|
+
# body, false otherwise.
|
|
211
379
|
def approaching_primary?
|
|
212
380
|
relative_position =
|
|
213
|
-
(geometric.position -
|
|
381
|
+
(geometric.position - primary_body_geometric.position).map(&:m)
|
|
214
382
|
relative_velocity =
|
|
215
|
-
(geometric.velocity -
|
|
383
|
+
(geometric.velocity - primary_body_geometric.velocity).map(&:mps)
|
|
216
384
|
radial_velocity_component = Astronoby::Util::Maths
|
|
217
385
|
.dot_product(relative_position, relative_velocity)
|
|
218
386
|
distance = Math.sqrt(
|
|
@@ -221,28 +389,39 @@ module Astronoby
|
|
|
221
389
|
radial_velocity_component / distance < 0
|
|
222
390
|
end
|
|
223
391
|
|
|
224
|
-
# @return [Boolean] True if the body is receding from
|
|
225
|
-
# body
|
|
392
|
+
# @return [Boolean] True if the body is receding from its primary
|
|
393
|
+
# body, false otherwise.
|
|
226
394
|
def receding_from_primary?
|
|
227
395
|
!approaching_primary?
|
|
228
396
|
end
|
|
229
397
|
|
|
230
398
|
private
|
|
231
399
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
# If a body does not require Sun data, it should override this method to
|
|
235
|
-
# return false.
|
|
236
|
-
def requires_sun_data?
|
|
237
|
-
true
|
|
400
|
+
def sun
|
|
401
|
+
@sun ||= Sun.new(instant: @instant, ephem: @ephem)
|
|
238
402
|
end
|
|
239
403
|
|
|
240
|
-
def
|
|
241
|
-
|
|
404
|
+
def primary_body_geometric
|
|
405
|
+
sun.geometric
|
|
242
406
|
end
|
|
243
407
|
|
|
244
|
-
def
|
|
245
|
-
|
|
408
|
+
def light_time_corrected_position
|
|
409
|
+
compute_light_time_correction unless @light_time_corrected_position
|
|
410
|
+
@light_time_corrected_position
|
|
411
|
+
end
|
|
412
|
+
|
|
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
|
+
)
|
|
246
425
|
end
|
|
247
426
|
|
|
248
427
|
# Source:
|
data/lib/astronoby/bodies/sun.rb
CHANGED
|
@@ -1,18 +1,77 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents the Sun. Provides twilight events, equinox/solstice
|
|
5
|
+
# calculations, and equation of time.
|
|
4
6
|
class Sun < SolarSystemBody
|
|
5
7
|
EQUATORIAL_RADIUS = Distance.from_meters(695_700_000)
|
|
6
8
|
ABSOLUTE_MAGNITUDE = -26.74
|
|
7
9
|
|
|
10
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
11
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
12
|
def self.ephemeris_segments(_ephem_source)
|
|
9
13
|
[[SOLAR_SYSTEM_BARYCENTER, SUN]]
|
|
10
14
|
end
|
|
11
15
|
|
|
16
|
+
# @return [Float] absolute magnitude
|
|
12
17
|
def self.absolute_magnitude
|
|
13
18
|
ABSOLUTE_MAGNITUDE
|
|
14
19
|
end
|
|
15
20
|
|
|
21
|
+
# @param observer [Astronoby::Observer] Observer for whom to calculate
|
|
22
|
+
# twilight events
|
|
23
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
24
|
+
# @param date [Date] Date for which to calculate twilight events (optional)
|
|
25
|
+
# @param start_time [Time] Start time for twilight event calculation
|
|
26
|
+
# (optional)
|
|
27
|
+
# @param end_time [Time] End time for twilight event calculation (optional)
|
|
28
|
+
# @param utc_offset [String] UTC offset for the given date (e.g., "+02:00")
|
|
29
|
+
# @return [Astronoby::TwilightEvent, Array<Astronoby::TwilightEvent>]
|
|
30
|
+
# Twilight events for the given date or time range.
|
|
31
|
+
def self.twilight_events(
|
|
32
|
+
observer:,
|
|
33
|
+
ephem:,
|
|
34
|
+
date: nil,
|
|
35
|
+
start_time: nil,
|
|
36
|
+
end_time: nil,
|
|
37
|
+
utc_offset: 0
|
|
38
|
+
)
|
|
39
|
+
calculator = TwilightCalculator.new(observer: observer, ephem: ephem)
|
|
40
|
+
if date
|
|
41
|
+
calculator.event_on(date, utc_offset: utc_offset)
|
|
42
|
+
else
|
|
43
|
+
calculator.events_between(start_time, end_time)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
48
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
49
|
+
# @return [Time] Time of the March equinox for the given year.
|
|
50
|
+
def self.march_equinox(year, ephem:)
|
|
51
|
+
EquinoxSolstice.march_equinox(year, ephem)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
55
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
56
|
+
# @return [Time] Time of the June solstice for the given year.
|
|
57
|
+
def self.june_solstice(year, ephem:)
|
|
58
|
+
EquinoxSolstice.june_solstice(year, ephem)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
62
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
63
|
+
# @return [Time] Time of the September equinox for the given year.
|
|
64
|
+
def self.september_equinox(year, ephem:)
|
|
65
|
+
EquinoxSolstice.september_equinox(year, ephem)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
69
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
70
|
+
# @return [Time] Time of the December solstice for the given year.
|
|
71
|
+
def self.december_solstice(year, ephem:)
|
|
72
|
+
EquinoxSolstice.december_solstice(year, ephem)
|
|
73
|
+
end
|
|
74
|
+
|
|
16
75
|
# Source:
|
|
17
76
|
# Title: Explanatory Supplement to the Astronomical Almanac
|
|
18
77
|
# Authors: Sean E. Urban and P. Kenneth Seidelmann
|
|
@@ -31,7 +90,7 @@ module Astronoby
|
|
|
31
90
|
# Edition: 2nd edition
|
|
32
91
|
# Chapter: 28 - Equation of Time
|
|
33
92
|
|
|
34
|
-
# @return [
|
|
93
|
+
# @return [Astronoby::Duration] Equation of time
|
|
35
94
|
def equation_of_time
|
|
36
95
|
right_ascension = apparent.equatorial.right_ascension
|
|
37
96
|
t = (@instant.julian_date - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_MILLENIA
|
|
@@ -44,7 +103,7 @@ module Astronoby
|
|
|
44
103
|
nutation = Nutation.new(instant: instant).nutation_in_longitude
|
|
45
104
|
obliquity = TrueObliquity.at(@instant)
|
|
46
105
|
|
|
47
|
-
(
|
|
106
|
+
seconds = (
|
|
48
107
|
Angle
|
|
49
108
|
.from_degrees(
|
|
50
109
|
l0 -
|
|
@@ -53,12 +112,28 @@ module Astronoby
|
|
|
53
112
|
nutation.degrees * obliquity.cos
|
|
54
113
|
).hours * Constants::SECONDS_PER_HOUR
|
|
55
114
|
).round
|
|
115
|
+
Duration.from_seconds(seconds)
|
|
56
116
|
end
|
|
57
117
|
|
|
58
|
-
|
|
118
|
+
# @return [nil] the Sun has no phase angle as seen from Earth
|
|
119
|
+
def phase_angle
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @return [Boolean] always false; the Sun has no primary body
|
|
124
|
+
def approaching_primary?
|
|
125
|
+
false
|
|
126
|
+
end
|
|
59
127
|
|
|
60
|
-
|
|
128
|
+
# @return [Boolean] always false; the Sun has no primary body
|
|
129
|
+
def receding_from_primary?
|
|
61
130
|
false
|
|
62
131
|
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def primary_body_geometric
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
63
138
|
end
|
|
64
139
|
end
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents Uranus.
|
|
4
5
|
class Uranus < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(25_559_000)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -7.11
|
|
8
|
+
ORBITAL_PERIOD = 30688.5
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Uranus 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, URANUS_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 Venus.
|
|
4
5
|
class Venus < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(6_051_800)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -4.384
|
|
8
|
+
ORBITAL_PERIOD = 224.701
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Venus 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, VENUS_BARYCENTER]]
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @return [Float] absolute magnitude
|
|
12
22
|
def self.absolute_magnitude
|
|
13
23
|
ABSOLUTE_MAGNITUDE
|
|
14
24
|
end
|
data/lib/astronoby/cache.rb
CHANGED
|
@@ -175,6 +175,19 @@ module Astronoby
|
|
|
175
175
|
[type, rounded_tt, *components]
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
+
# Generate a cache key for a UTC time, without rounding. Sidereal time
|
|
179
|
+
# moves by about 15 arcseconds per second, so unlike nutation it can't
|
|
180
|
+
# share an entry with a nearby instant.
|
|
181
|
+
# @param type [Symbol] The calculation type (e.g., :gmst)
|
|
182
|
+
# @param utc [Time] The UTC time
|
|
183
|
+
# @param components [Array] Additional components for the key
|
|
184
|
+
# @return [Array, nil] The complete cache key, nil if cache is disabled
|
|
185
|
+
def generate_for_utc(type, utc, *components)
|
|
186
|
+
return nil unless Astronoby.configuration.cache_enabled?
|
|
187
|
+
|
|
188
|
+
[type, utc.to_r, *components]
|
|
189
|
+
end
|
|
190
|
+
|
|
178
191
|
# Round terrestrial time to specified precision
|
|
179
192
|
# @param tt [Numeric] Terrestrial time value
|
|
180
193
|
# @param precision [Numeric] Precision in seconds
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class Center
|
|
5
|
+
BARYCENTRIC = :barycentric
|
|
6
|
+
GEOCENTRIC = :geocentric
|
|
7
|
+
TOPOCENTRIC = :topocentric
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# @return [Astronoby::Center] the Solar System barycenter
|
|
11
|
+
def barycentric
|
|
12
|
+
@barycentric ||= new(kind: BARYCENTRIC)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [Astronoby::Center] the Earth's center
|
|
16
|
+
def geocentric
|
|
17
|
+
@geocentric ||= new(kind: GEOCENTRIC)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param observer [Astronoby::Observer] the observer
|
|
21
|
+
# @return [Astronoby::Center] a center at the observer's location
|
|
22
|
+
def topocentric(observer)
|
|
23
|
+
new(kind: TOPOCENTRIC, observer: observer)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Symbol] the kind of center
|
|
28
|
+
attr_reader :kind
|
|
29
|
+
|
|
30
|
+
# @return [Astronoby::Observer, nil] the observer, for topocentric centers
|
|
31
|
+
attr_reader :observer
|
|
32
|
+
|
|
33
|
+
# @param kind [Symbol] one of BARYCENTRIC, GEOCENTRIC, TOPOCENTRIC
|
|
34
|
+
# @param observer [Astronoby::Observer, nil] the observer, for topocentric
|
|
35
|
+
def initialize(kind:, observer: nil)
|
|
36
|
+
@kind = kind
|
|
37
|
+
@observer = observer
|
|
38
|
+
freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Boolean] true if the center is the Solar System barycenter
|
|
42
|
+
def barycentric?
|
|
43
|
+
@kind == BARYCENTRIC
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Boolean] true if the center is the Earth's center
|
|
47
|
+
def geocentric?
|
|
48
|
+
@kind == GEOCENTRIC
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] true if the center is at an observer's location
|
|
52
|
+
def topocentric?
|
|
53
|
+
@kind == TOPOCENTRIC
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Boolean] true if the center depends on a specific observer
|
|
57
|
+
def observer_dependent?
|
|
58
|
+
topocentric?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param other [Astronoby::Center] center to compare with
|
|
62
|
+
# @return [Boolean] true if both centers are equivalent
|
|
63
|
+
def ==(other)
|
|
64
|
+
other.is_a?(self.class) &&
|
|
65
|
+
kind == other.kind &&
|
|
66
|
+
location_key == other.location_key
|
|
67
|
+
end
|
|
68
|
+
alias_method :eql?, :==
|
|
69
|
+
|
|
70
|
+
# @return [Integer] hash value
|
|
71
|
+
def hash
|
|
72
|
+
[self.class, @kind, location_key].hash
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
protected
|
|
76
|
+
|
|
77
|
+
# @return [Array, nil] the geometric location key, or nil if not topocentric
|
|
78
|
+
def location_key
|
|
79
|
+
return unless @observer
|
|
80
|
+
|
|
81
|
+
[@observer.latitude, @observer.longitude, @observer.elevation]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|