astronoby 0.6.0 → 0.8.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 -0
- data/.standard.yml +1 -0
- data/CHANGELOG.md +203 -3
- data/README.md +69 -288
- data/UPGRADING.md +267 -0
- data/docs/README.md +196 -0
- data/docs/angles.md +137 -0
- data/docs/celestial_bodies.md +107 -0
- data/docs/configuration.md +98 -0
- data/docs/coordinates.md +167 -0
- data/docs/ephem.md +85 -0
- data/docs/equinoxes_solstices_times.md +31 -0
- data/docs/glossary.md +152 -0
- data/docs/instant.md +139 -0
- data/docs/moon_phases.md +79 -0
- data/docs/observer.md +65 -0
- data/docs/reference_frames.md +138 -0
- data/docs/rise_transit_set_times.md +119 -0
- data/docs/twilight_times.md +123 -0
- data/lib/astronoby/aberration.rb +56 -31
- data/lib/astronoby/angle.rb +20 -16
- data/lib/astronoby/angles/dms.rb +2 -2
- data/lib/astronoby/angles/hms.rb +2 -2
- data/lib/astronoby/bodies/earth.rb +62 -0
- data/lib/astronoby/bodies/jupiter.rb +28 -0
- data/lib/astronoby/bodies/mars.rb +28 -0
- data/lib/astronoby/bodies/mercury.rb +32 -0
- data/lib/astronoby/bodies/moon.rb +51 -298
- data/lib/astronoby/bodies/neptune.rb +32 -0
- data/lib/astronoby/bodies/saturn.rb +37 -0
- data/lib/astronoby/bodies/solar_system_body.rb +232 -0
- data/lib/astronoby/bodies/sun.rb +33 -214
- data/lib/astronoby/bodies/uranus.rb +16 -0
- data/lib/astronoby/bodies/venus.rb +36 -0
- data/lib/astronoby/cache.rb +188 -0
- data/lib/astronoby/configuration.rb +92 -0
- data/lib/astronoby/constants.rb +17 -2
- data/lib/astronoby/constellation.rb +12 -0
- data/lib/astronoby/constellations/data.rb +42 -0
- data/lib/astronoby/constellations/finder.rb +35 -0
- data/lib/astronoby/constellations/repository.rb +20 -0
- data/lib/astronoby/coordinates/ecliptic.rb +2 -37
- data/lib/astronoby/coordinates/equatorial.rb +28 -10
- data/lib/astronoby/coordinates/horizontal.rb +0 -46
- data/lib/astronoby/corrections/light_time_delay.rb +90 -0
- data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
- data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
- data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
- data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
- data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
- data/lib/astronoby/deflection.rb +187 -0
- data/lib/astronoby/distance.rb +9 -0
- data/lib/astronoby/ephem.rb +39 -0
- data/lib/astronoby/equinox_solstice.rb +22 -19
- data/lib/astronoby/errors.rb +4 -0
- data/lib/astronoby/events/moon_phases.rb +15 -13
- data/lib/astronoby/events/rise_transit_set_calculator.rb +376 -0
- data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
- data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
- data/lib/astronoby/events/twilight_calculator.rb +221 -0
- data/lib/astronoby/events/twilight_event.rb +28 -0
- data/lib/astronoby/events/twilight_events.rb +22 -115
- data/lib/astronoby/instant.rb +176 -0
- data/lib/astronoby/julian_date.rb +78 -0
- data/lib/astronoby/mean_obliquity.rb +24 -13
- data/lib/astronoby/nutation.rb +235 -42
- data/lib/astronoby/observer.rb +55 -0
- data/lib/astronoby/precession.rb +102 -18
- data/lib/astronoby/reference_frame.rb +50 -0
- data/lib/astronoby/reference_frames/apparent.rb +60 -0
- data/lib/astronoby/reference_frames/astrometric.rb +21 -0
- data/lib/astronoby/reference_frames/geometric.rb +20 -0
- data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
- data/lib/astronoby/reference_frames/topocentric.rb +72 -0
- data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
- data/lib/astronoby/true_obliquity.rb +3 -3
- data/lib/astronoby/util/maths.rb +70 -73
- data/lib/astronoby/util/time.rb +455 -32
- data/lib/astronoby/vector.rb +36 -0
- data/lib/astronoby/velocity.rb +116 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +33 -5
- metadata +117 -24
- data/.tool-versions +0 -1
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -80
- data/benchmark/README.md +0 -131
- data/benchmark/benchmark.rb +0 -259
- data/benchmark/data/imcce.csv.zip +0 -0
- data/benchmark/data/sun_calc.csv.zip +0 -0
- data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
- data/lib/astronoby/epoch.rb +0 -22
- data/lib/astronoby/events/observation_events.rb +0 -285
- data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -218
- data/lib/astronoby/util/astrodynamics.rb +0 -60
@@ -73,23 +73,23 @@ module Astronoby
|
|
73
73
|
[8, 15.45, 16859.074]
|
74
74
|
].freeze
|
75
75
|
|
76
|
-
def self.march_equinox(year)
|
77
|
-
new(year, MARCH_EQUINOX).
|
76
|
+
def self.march_equinox(year, ephem)
|
77
|
+
new(year, MARCH_EQUINOX, ephem).time
|
78
78
|
end
|
79
79
|
|
80
|
-
def self.june_solstice(year)
|
81
|
-
new(year, JUNE_SOLSTICE).
|
80
|
+
def self.june_solstice(year, ephem)
|
81
|
+
new(year, JUNE_SOLSTICE, ephem).time
|
82
82
|
end
|
83
83
|
|
84
|
-
def self.september_equinox(year)
|
85
|
-
new(year, SEPTEMBER_EQUINOX).
|
84
|
+
def self.september_equinox(year, ephem)
|
85
|
+
new(year, SEPTEMBER_EQUINOX, ephem).time
|
86
86
|
end
|
87
87
|
|
88
|
-
def self.december_solstice(year)
|
89
|
-
new(year, DECEMBER_SOLSTICE).
|
88
|
+
def self.december_solstice(year, ephem)
|
89
|
+
new(year, DECEMBER_SOLSTICE, ephem).time
|
90
90
|
end
|
91
91
|
|
92
|
-
def initialize(year, event)
|
92
|
+
def initialize(year, event, ephem)
|
93
93
|
unless EVENTS.include?(event)
|
94
94
|
raise UnsupportedEventError.new(
|
95
95
|
"Expected a format between #{EVENTS.join(", ")}, got #{event}"
|
@@ -98,10 +98,19 @@ module Astronoby
|
|
98
98
|
|
99
99
|
@event = event
|
100
100
|
@year = (year.to_i - 2000) / 1000.0
|
101
|
+
uncorrected_time = compute
|
102
|
+
@instant = Instant.from_time(uncorrected_time)
|
103
|
+
@sun = Sun.new(ephem: ephem, instant: @instant)
|
101
104
|
end
|
102
105
|
|
106
|
+
def time
|
107
|
+
Instant.from_terrestrial_time(@instant.tt + corrected).to_time.round
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
103
112
|
def compute
|
104
|
-
t = (julian_day -
|
113
|
+
t = (julian_day - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
|
105
114
|
w = Angle.from_degrees(35999.373 * t) - Angle.from_degrees(2.47)
|
106
115
|
delta = 1 +
|
107
116
|
0.0334 * w.cos +
|
@@ -113,13 +122,10 @@ module Astronoby
|
|
113
122
|
|
114
123
|
delta_days = 0.00001 * s / delta
|
115
124
|
epoch = julian_day + delta_days
|
116
|
-
epoch += correction(epoch)
|
117
125
|
|
118
|
-
|
126
|
+
Instant.from_terrestrial_time(epoch).to_time
|
119
127
|
end
|
120
128
|
|
121
|
-
private
|
122
|
-
|
123
129
|
def julian_day
|
124
130
|
component = JDE_COMPONENTS[@event]
|
125
131
|
component[0] +
|
@@ -129,11 +135,8 @@ module Astronoby
|
|
129
135
|
component[4] * @year**4
|
130
136
|
end
|
131
137
|
|
132
|
-
def
|
133
|
-
|
134
|
-
sun = Sun.new(time: time)
|
135
|
-
longitude = sun.apparent_ecliptic_coordinates.longitude
|
136
|
-
|
138
|
+
def corrected
|
139
|
+
longitude = @sun.apparent.ecliptic.longitude
|
137
140
|
58 * Angle.from_degrees(@event * 90 - longitude.degrees).sin
|
138
141
|
end
|
139
142
|
end
|
data/lib/astronoby/errors.rb
CHANGED
@@ -15,16 +15,19 @@ module Astronoby
|
|
15
15
|
# @param month [Integer] Requested month
|
16
16
|
# @return [Array<Astronoby::MoonPhase>] List of Moon phases
|
17
17
|
def self.phases_for(year:, month:)
|
18
|
-
[
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
Astronoby.cache.fetch([:moon_phases, year, month]) do
|
19
|
+
[
|
20
|
+
MoonPhase.first_quarter(new(year, month, :first_quarter, -0.75).time),
|
21
|
+
MoonPhase.full_moon(new(year, month, :full_moon, -0.5).time),
|
22
|
+
MoonPhase.last_quarter(new(year, month, :last_quarter, -0.25).time),
|
23
|
+
MoonPhase.new_moon(new(year, month, :new_moon, 0).time),
|
24
|
+
MoonPhase.first_quarter(new(year, month, :first_quarter, 0.25).time),
|
25
|
+
MoonPhase.full_moon(new(year, month, :full_moon, 0.5).time),
|
26
|
+
MoonPhase.last_quarter(new(year, month, :last_quarter, 0.75).time),
|
27
|
+
MoonPhase.new_moon(new(year, month, :new_moon, 1).time),
|
28
|
+
MoonPhase.first_quarter(new(year, month, :first_quarter, 1.25).time)
|
29
|
+
].select { _1.time.month == month }
|
30
|
+
end
|
28
31
|
end
|
29
32
|
|
30
33
|
# @param year [Integer] Requested year
|
@@ -42,13 +45,12 @@ module Astronoby
|
|
42
45
|
def time
|
43
46
|
correction = moon_phases_periodic_terms
|
44
47
|
.public_send(:"#{@phase}_correction")
|
45
|
-
terrestrial_time =
|
48
|
+
terrestrial_time = Instant.from_terrestrial_time(
|
46
49
|
julian_ephemeris_day +
|
47
50
|
correction +
|
48
51
|
moon_phases_periodic_terms.additional_corrections
|
49
52
|
)
|
50
|
-
|
51
|
-
(terrestrial_time - delta).round
|
53
|
+
terrestrial_time.to_time.round
|
52
54
|
end
|
53
55
|
|
54
56
|
private
|
@@ -0,0 +1,376 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class RiseTransitSetCalculator
|
5
|
+
class PotentialEvent
|
6
|
+
attr_reader :hour_angle, :can_occur
|
7
|
+
|
8
|
+
def initialize(hour_angle, can_occur)
|
9
|
+
@hour_angle = hour_angle
|
10
|
+
@can_occur = can_occur
|
11
|
+
end
|
12
|
+
|
13
|
+
def negated
|
14
|
+
self.class.new(-@hour_angle, @can_occur)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
TAU = Math::PI * 2
|
19
|
+
SAMPLE_THRESHOLD = 0.8
|
20
|
+
REFINEMENT_ITERATIONS = 3
|
21
|
+
MIN_TIME_ADJUSTMENT = Constants::MICROSECOND_IN_DAYS
|
22
|
+
STANDARD_REFRACTION_ANGLE = -Angle.from_dms(0, 34, 0)
|
23
|
+
SUN_REFRACTION_ANGLE = -Angle.from_dms(0, 50, 0)
|
24
|
+
EVENT_TYPES = [:rising, :transit, :setting].freeze
|
25
|
+
|
26
|
+
def initialize(body:, observer:, ephem:)
|
27
|
+
@body = body
|
28
|
+
@observer = observer
|
29
|
+
@ephem = ephem
|
30
|
+
end
|
31
|
+
|
32
|
+
def event_on(date, utc_offset: 0)
|
33
|
+
events = events_on(date, utc_offset: utc_offset)
|
34
|
+
RiseTransitSetEvent.new(
|
35
|
+
events.rising_times.first,
|
36
|
+
events.transit_times.first,
|
37
|
+
events.setting_times.first
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def events_on(date, utc_offset: 0)
|
42
|
+
start_time = Time.new(
|
43
|
+
date.year,
|
44
|
+
date.month,
|
45
|
+
date.day,
|
46
|
+
0, 0, 0, utc_offset
|
47
|
+
)
|
48
|
+
end_time = Time.new(
|
49
|
+
date.year,
|
50
|
+
date.month,
|
51
|
+
date.day,
|
52
|
+
23, 59, 59, utc_offset
|
53
|
+
)
|
54
|
+
events_between(start_time, end_time)
|
55
|
+
end
|
56
|
+
|
57
|
+
def events_between(start_time, end_time)
|
58
|
+
reset_state
|
59
|
+
@start_instant = Instant.from_time(start_time)
|
60
|
+
@end_instant = Instant.from_time(end_time)
|
61
|
+
events
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def events
|
67
|
+
rising_events = calculate_initial_positions.map do |position|
|
68
|
+
calculate_rising_event(position)
|
69
|
+
end
|
70
|
+
setting_events = rising_events.map(&:negated)
|
71
|
+
transit_event = PotentialEvent.new(Angle.zero, true)
|
72
|
+
|
73
|
+
event_data = {
|
74
|
+
rising: rising_events,
|
75
|
+
transit: [transit_event],
|
76
|
+
setting: setting_events
|
77
|
+
}
|
78
|
+
|
79
|
+
results = EVENT_TYPES.each_with_object({}) do |event_type, results|
|
80
|
+
results[event_type] = calculate_event_times(
|
81
|
+
event_type,
|
82
|
+
event_data[event_type]
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
RiseTransitSetEvents.new(
|
87
|
+
results[:rising],
|
88
|
+
results[:transit],
|
89
|
+
results[:setting]
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
def calculate_rising_event(position)
|
94
|
+
declination = position.equatorial.declination
|
95
|
+
latitude = @observer.latitude
|
96
|
+
altitude = horizon_angle(position.distance)
|
97
|
+
|
98
|
+
# Calculate the unmodified ratio to check if the body rises/sets
|
99
|
+
numerator = altitude.sin - latitude.sin * declination.sin
|
100
|
+
denominator = latitude.cos * declination.cos
|
101
|
+
ratio = numerator / denominator
|
102
|
+
|
103
|
+
# Determine if the body can rise/set and calculate the hour angle
|
104
|
+
can_rise_set = ratio.abs <= 1.0
|
105
|
+
angle = can_rise_set ? -Angle.acos(ratio.clamp(-1.0, 1.0)) : Angle.zero
|
106
|
+
|
107
|
+
PotentialEvent.new(angle, can_rise_set)
|
108
|
+
end
|
109
|
+
|
110
|
+
def calculate_event_times(event_type, events)
|
111
|
+
desired_hour_angles = events.map(&:hour_angle)
|
112
|
+
|
113
|
+
# Calculate differences between current and desired hour angles
|
114
|
+
angle_differences = calculate_angle_differences(
|
115
|
+
calculate_initial_hour_angles,
|
116
|
+
desired_hour_angles
|
117
|
+
)
|
118
|
+
|
119
|
+
# Find intervals where the body crosses the desired hour angle
|
120
|
+
crossing_indexes = find_crossing_intervals(angle_differences)
|
121
|
+
|
122
|
+
# Skip if no relevant crossing points found
|
123
|
+
return [] if crossing_indexes.empty?
|
124
|
+
|
125
|
+
valid_crossings = if events.size == 1
|
126
|
+
# For transit (single event), all crossings are valid
|
127
|
+
crossing_indexes.map { true }
|
128
|
+
else
|
129
|
+
# For rise/set, check if each crossing corresponds to a valid event
|
130
|
+
crossing_indexes.map { |i| events[i].can_occur }
|
131
|
+
end
|
132
|
+
|
133
|
+
old_hour_angles = calculate_initial_hour_angles
|
134
|
+
.values_at(*crossing_indexes)
|
135
|
+
old_instants = sample_instants.values_at(*crossing_indexes)
|
136
|
+
|
137
|
+
# Initial estimate of event times using linear interpolation
|
138
|
+
new_instants = interpolate_initial_times(
|
139
|
+
crossing_indexes,
|
140
|
+
angle_differences
|
141
|
+
)
|
142
|
+
|
143
|
+
# Refine the estimates through iteration
|
144
|
+
refined_times = refine_time_estimates(
|
145
|
+
event_type,
|
146
|
+
new_instants,
|
147
|
+
old_hour_angles,
|
148
|
+
old_instants
|
149
|
+
)
|
150
|
+
|
151
|
+
# Filter out times for bodies that never rise/set
|
152
|
+
refined_times
|
153
|
+
.zip(valid_crossings)
|
154
|
+
.filter_map { |time, valid| time if valid }
|
155
|
+
end
|
156
|
+
|
157
|
+
def sample_count
|
158
|
+
@sample_count ||=
|
159
|
+
((@end_instant.tt - @start_instant.tt) / SAMPLE_THRESHOLD).ceil + 1
|
160
|
+
end
|
161
|
+
|
162
|
+
def sample_instants
|
163
|
+
@sample_instants ||= Util::Maths.linspace(
|
164
|
+
@start_instant.tt,
|
165
|
+
@end_instant.tt,
|
166
|
+
sample_count
|
167
|
+
).map { |tt| Instant.from_terrestrial_time(tt) }
|
168
|
+
end
|
169
|
+
|
170
|
+
def calculate_initial_positions
|
171
|
+
@initial_positions ||= calculate_positions_at_instants(sample_instants)
|
172
|
+
end
|
173
|
+
|
174
|
+
def calculate_initial_hour_angles
|
175
|
+
@initial_hour_angles ||=
|
176
|
+
calculate_hour_angles(calculate_initial_positions)
|
177
|
+
end
|
178
|
+
|
179
|
+
def calculate_angle_differences(hour_angles, angles)
|
180
|
+
hour_angles.each_with_index.map do |hour_angle, i|
|
181
|
+
angle = (angles.size == 1) ? angles[0] : angles[i]
|
182
|
+
Angle.from_radians((angle - hour_angle).radians % TAU)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def find_crossing_intervals(differences)
|
187
|
+
differences
|
188
|
+
.each_cons(2)
|
189
|
+
.map { |a, b| b - a }
|
190
|
+
.each_with_index
|
191
|
+
.filter_map { |diff, i| i if diff.radians > 0.0 }
|
192
|
+
end
|
193
|
+
|
194
|
+
def interpolate_initial_times(crossing_indexes, angle_differences)
|
195
|
+
crossing_indexes.map do |index|
|
196
|
+
a = angle_differences[index].radians
|
197
|
+
b = TAU - angle_differences[index + 1].radians
|
198
|
+
c = sample_instants[index].tt
|
199
|
+
d = sample_instants[index + 1].tt
|
200
|
+
|
201
|
+
# Linear interpolation formula
|
202
|
+
tt = (b * c + a * d) / (a + b)
|
203
|
+
Instant.from_terrestrial_time(tt)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def refine_time_estimates(
|
208
|
+
event_type,
|
209
|
+
new_instants,
|
210
|
+
old_hour_angles,
|
211
|
+
old_instants
|
212
|
+
)
|
213
|
+
REFINEMENT_ITERATIONS.times do |iteration|
|
214
|
+
# Calculate positions at current estimates
|
215
|
+
apparent_positions = calculate_positions_at_instants(new_instants)
|
216
|
+
|
217
|
+
# Calculate current hour angles
|
218
|
+
current_hour_angles = calculate_hour_angles(apparent_positions)
|
219
|
+
|
220
|
+
# Calculate desired hour angles based on current positions
|
221
|
+
desired_hour_angles = calculate_desired_hour_angles(
|
222
|
+
event_type,
|
223
|
+
apparent_positions
|
224
|
+
)
|
225
|
+
|
226
|
+
# Calculate hour angle adjustments
|
227
|
+
hour_angle_adjustments = calculate_hour_angle_adjustments(
|
228
|
+
current_hour_angles,
|
229
|
+
desired_hour_angles
|
230
|
+
)
|
231
|
+
|
232
|
+
# Calculate hour angle changes for rate determination
|
233
|
+
hour_angle_changes = calculate_hour_angle_changes(
|
234
|
+
current_hour_angles,
|
235
|
+
old_hour_angles,
|
236
|
+
iteration
|
237
|
+
)
|
238
|
+
|
239
|
+
# Calculate time differences
|
240
|
+
time_differences_in_days = new_instants.each_with_index.map do |instant, i|
|
241
|
+
instant.tt - old_instants[i].tt
|
242
|
+
end
|
243
|
+
|
244
|
+
# Calculate hour angle rate (radians per day)
|
245
|
+
hour_angle_rates = hour_angle_changes.each_with_index.map do |angle, i|
|
246
|
+
denominator = time_differences_in_days[i]
|
247
|
+
angle.radians / denominator
|
248
|
+
end
|
249
|
+
|
250
|
+
# Store current values for next iteration
|
251
|
+
old_hour_angles = current_hour_angles
|
252
|
+
old_instants = new_instants
|
253
|
+
|
254
|
+
# Calculate time adjustments
|
255
|
+
time_adjustments = hour_angle_adjustments
|
256
|
+
.each_with_index
|
257
|
+
.map do |angle, i|
|
258
|
+
ratio = angle.radians / hour_angle_rates[i]
|
259
|
+
time_adjustment = (ratio.nan? || ratio.infinite?) ? 0 : ratio
|
260
|
+
[time_adjustment, MIN_TIME_ADJUSTMENT].max
|
261
|
+
end
|
262
|
+
|
263
|
+
# Apply time adjustments
|
264
|
+
new_instants = new_instants.each_with_index.map do |instant, i|
|
265
|
+
Instant.from_terrestrial_time(instant.tt + time_adjustments[i])
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
new_instants.map { _1.to_time.round }
|
270
|
+
end
|
271
|
+
|
272
|
+
def calculate_positions_at_instants(instants)
|
273
|
+
if Astronoby.configuration.cache_enabled?
|
274
|
+
instants.map do |instant|
|
275
|
+
cache_key = CacheKey.generate(
|
276
|
+
:observed_by,
|
277
|
+
instant,
|
278
|
+
@body.to_s,
|
279
|
+
@observer.hash
|
280
|
+
)
|
281
|
+
Astronoby.cache.fetch(cache_key) do
|
282
|
+
@body
|
283
|
+
.new(instant: instant, ephem: @ephem)
|
284
|
+
.observed_by(@observer)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
else
|
288
|
+
@positions_cache ||= {}
|
289
|
+
precision = Astronoby.configuration.cache_precision(:observed_by)
|
290
|
+
instants.map do |instant|
|
291
|
+
rounded_instant = Instant.from_terrestrial_time(
|
292
|
+
instant.tt.round(precision)
|
293
|
+
)
|
294
|
+
@positions_cache[rounded_instant] ||= @body
|
295
|
+
.new(instant: rounded_instant, ephem: @ephem)
|
296
|
+
.observed_by(@observer)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def calculate_hour_angles(positions)
|
302
|
+
positions.map do |position|
|
303
|
+
position.equatorial.compute_hour_angle(
|
304
|
+
time: position.instant.to_time,
|
305
|
+
longitude: @observer.longitude
|
306
|
+
)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
def calculate_desired_hour_angles(event_type, positions)
|
311
|
+
positions.map do |position|
|
312
|
+
if event_type == :transit
|
313
|
+
Angle.zero
|
314
|
+
else
|
315
|
+
declination = position.equatorial.declination
|
316
|
+
ha = rising_hour_angle(
|
317
|
+
@observer.latitude,
|
318
|
+
declination,
|
319
|
+
horizon_angle(position.distance)
|
320
|
+
)
|
321
|
+
(event_type == :rising) ? ha : -ha
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def calculate_hour_angle_adjustments(current_angles, angles)
|
327
|
+
current_angles.each_with_index.map do |angle, i|
|
328
|
+
radians = ((angles[i] - angle).radians + Math::PI) % TAU - Math::PI
|
329
|
+
Angle.from_radians(radians)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def calculate_hour_angle_changes(current_angles, old_angles, iteration)
|
334
|
+
current_angles.each_with_index.map do |angle, i|
|
335
|
+
radians = angle.radians - old_angles[i].radians
|
336
|
+
|
337
|
+
if iteration == 0
|
338
|
+
radians %= TAU
|
339
|
+
else
|
340
|
+
radians = (radians + Math::PI) % TAU - Math::PI
|
341
|
+
end
|
342
|
+
|
343
|
+
Angle.from_radians(radians)
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def rising_hour_angle(latitude, declination, altitude)
|
348
|
+
numerator = altitude.sin - latitude.sin * declination.sin
|
349
|
+
denominator = latitude.cos * declination.cos
|
350
|
+
ratio = (numerator / denominator).clamp(-1.0, 1.0)
|
351
|
+
-Angle.acos(ratio)
|
352
|
+
end
|
353
|
+
|
354
|
+
def horizon_angle(distance)
|
355
|
+
case @body.name
|
356
|
+
when "Astronoby::Sun"
|
357
|
+
SUN_REFRACTION_ANGLE
|
358
|
+
when "Astronoby::Moon"
|
359
|
+
STANDARD_REFRACTION_ANGLE -
|
360
|
+
Angle.from_radians(Moon::EQUATORIAL_RADIUS.m / distance.m)
|
361
|
+
else
|
362
|
+
STANDARD_REFRACTION_ANGLE
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def reset_state
|
367
|
+
@initial_hour_angles = nil
|
368
|
+
@initial_positions = nil
|
369
|
+
@sample_count = nil
|
370
|
+
@sample_instants = nil
|
371
|
+
@start_instant = nil
|
372
|
+
@end_instant = nil
|
373
|
+
@positions_cache = nil
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class RiseTransitSetEvent
|
5
|
+
attr_reader :rising_time, :transit_time, :setting_time
|
6
|
+
|
7
|
+
def initialize(rising, transit, setting)
|
8
|
+
@rising_time = rising
|
9
|
+
@transit_time = transit
|
10
|
+
@setting_time = setting
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class RiseTransitSetEvents
|
5
|
+
attr_reader :rising_times, :transit_times, :setting_times
|
6
|
+
|
7
|
+
def initialize(risings, transits, settings)
|
8
|
+
@rising_times = risings
|
9
|
+
@transit_times = transits
|
10
|
+
@setting_times = settings
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|