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
@@ -0,0 +1,221 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class TwilightCalculator
|
5
|
+
TWILIGHTS = [
|
6
|
+
CIVIL = :civil,
|
7
|
+
NAUTICAL = :nautical,
|
8
|
+
ASTRONOMICAL = :astronomical
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
TWILIGHT_ANGLES = {
|
12
|
+
CIVIL => Angle.from_degrees(96),
|
13
|
+
NAUTICAL => Angle.from_degrees(102),
|
14
|
+
ASTRONOMICAL => Angle.from_degrees(108)
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
PERIODS_OF_THE_DAY = [
|
18
|
+
MORNING = :morning,
|
19
|
+
EVENING = :evening
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
def initialize(observer:, ephem:)
|
23
|
+
@observer = observer
|
24
|
+
@ephem = ephem
|
25
|
+
end
|
26
|
+
|
27
|
+
def event_on(date, utc_offset: 0)
|
28
|
+
start_time = Time
|
29
|
+
.new(date.year, date.month, date.day, 0, 0, 0, utc_offset)
|
30
|
+
end_time = Time
|
31
|
+
.new(date.year, date.month, date.day, 23, 59, 59, utc_offset)
|
32
|
+
events = events_between(start_time, end_time)
|
33
|
+
|
34
|
+
TwilightEvent.new(
|
35
|
+
morning_civil_twilight_time:
|
36
|
+
events.morning_civil_twilight_times.first,
|
37
|
+
evening_civil_twilight_time:
|
38
|
+
events.evening_civil_twilight_times.first,
|
39
|
+
morning_nautical_twilight_time:
|
40
|
+
events.morning_nautical_twilight_times.first,
|
41
|
+
evening_nautical_twilight_time:
|
42
|
+
events.evening_nautical_twilight_times.first,
|
43
|
+
morning_astronomical_twilight_time:
|
44
|
+
events.morning_astronomical_twilight_times.first,
|
45
|
+
evening_astronomical_twilight_time:
|
46
|
+
events.evening_astronomical_twilight_times.first
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def events_between(start_time, end_time)
|
51
|
+
rts_events = Astronoby::RiseTransitSetCalculator.new(
|
52
|
+
body: Sun,
|
53
|
+
observer: @observer,
|
54
|
+
ephem: @ephem
|
55
|
+
).events_between(start_time, end_time)
|
56
|
+
|
57
|
+
equatorial_by_time = {}
|
58
|
+
|
59
|
+
(rts_events.rising_times + rts_events.setting_times)
|
60
|
+
.compact
|
61
|
+
.each do |event_time|
|
62
|
+
rounded_time = event_time.round
|
63
|
+
next if equatorial_by_time.key?(rounded_time)
|
64
|
+
|
65
|
+
instant = Instant.from_time(rounded_time)
|
66
|
+
sun_at_time = Sun.new(instant: instant, ephem: @ephem)
|
67
|
+
equatorial_by_time[rounded_time] = sun_at_time.apparent.equatorial
|
68
|
+
end
|
69
|
+
|
70
|
+
morning_civil = []
|
71
|
+
evening_civil = []
|
72
|
+
morning_nautical = []
|
73
|
+
evening_nautical = []
|
74
|
+
morning_astronomical = []
|
75
|
+
evening_astronomical = []
|
76
|
+
|
77
|
+
arrays_by_period = {
|
78
|
+
MORNING => {
|
79
|
+
CIVIL => morning_civil,
|
80
|
+
NAUTICAL => morning_nautical,
|
81
|
+
ASTRONOMICAL => morning_astronomical
|
82
|
+
},
|
83
|
+
EVENING => {
|
84
|
+
CIVIL => evening_civil,
|
85
|
+
NAUTICAL => evening_nautical,
|
86
|
+
ASTRONOMICAL => evening_astronomical
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
[
|
91
|
+
[rts_events.rising_times, MORNING],
|
92
|
+
[rts_events.setting_times, EVENING]
|
93
|
+
].each do |times, period|
|
94
|
+
times.each do |event_time|
|
95
|
+
next unless event_time
|
96
|
+
|
97
|
+
equatorial_coordinates = equatorial_by_time[event_time.round]
|
98
|
+
TWILIGHT_ANGLES.each do |twilight, angle|
|
99
|
+
arrays_by_period[period][twilight] << compute_twilight_time_from(
|
100
|
+
period,
|
101
|
+
angle,
|
102
|
+
event_time,
|
103
|
+
equatorial_coordinates
|
104
|
+
)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
within_range = ->(time) { time && time >= start_time && time <= end_time }
|
110
|
+
|
111
|
+
TwilightEvents.new(
|
112
|
+
morning_civil.select(&within_range),
|
113
|
+
evening_civil.select(&within_range),
|
114
|
+
morning_nautical.select(&within_range),
|
115
|
+
evening_nautical.select(&within_range),
|
116
|
+
morning_astronomical.select(&within_range),
|
117
|
+
evening_astronomical.select(&within_range)
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def time_for_zenith_angle(
|
122
|
+
date:,
|
123
|
+
period_of_the_day:,
|
124
|
+
zenith_angle:,
|
125
|
+
utc_offset: 0
|
126
|
+
)
|
127
|
+
unless PERIODS_OF_THE_DAY.include?(period_of_the_day)
|
128
|
+
raise IncompatibleArgumentsError,
|
129
|
+
"Only #{PERIODS_OF_THE_DAY.join(" or ")} are allowed as " \
|
130
|
+
"period_of_the_day, got #{period_of_the_day}"
|
131
|
+
end
|
132
|
+
|
133
|
+
observation_events = get_observation_events(date, utc_offset: utc_offset)
|
134
|
+
midday_instant = create_midday_instant(date, utc_offset: utc_offset)
|
135
|
+
sun_at_midday = Sun.new(instant: midday_instant, ephem: @ephem)
|
136
|
+
equatorial_coordinates = sun_at_midday.apparent.equatorial
|
137
|
+
|
138
|
+
compute_twilight_time(
|
139
|
+
period_of_the_day,
|
140
|
+
zenith_angle,
|
141
|
+
observation_events,
|
142
|
+
equatorial_coordinates
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def create_midday_instant(date, utc_offset: 0)
|
149
|
+
time = Time.new(date.year, date.month, date.day, 12, 0, 0, utc_offset)
|
150
|
+
Instant.from_time(time)
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_observation_events(date, utc_offset: 0)
|
154
|
+
Astronoby::RiseTransitSetCalculator.new(
|
155
|
+
body: Sun,
|
156
|
+
observer: @observer,
|
157
|
+
ephem: @ephem
|
158
|
+
).event_on(date, utc_offset: utc_offset)
|
159
|
+
end
|
160
|
+
|
161
|
+
def compute_twilight_time(
|
162
|
+
period_of_the_day,
|
163
|
+
zenith_angle,
|
164
|
+
observation_events,
|
165
|
+
equatorial_coordinates
|
166
|
+
)
|
167
|
+
period_time = if period_of_the_day == MORNING
|
168
|
+
observation_events.rising_time
|
169
|
+
else
|
170
|
+
observation_events.setting_time
|
171
|
+
end
|
172
|
+
|
173
|
+
compute_twilight_time_from(
|
174
|
+
period_of_the_day,
|
175
|
+
zenith_angle,
|
176
|
+
period_time,
|
177
|
+
equatorial_coordinates
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
def compute_twilight_time_from(
|
182
|
+
period_of_the_day,
|
183
|
+
zenith_angle,
|
184
|
+
period_time,
|
185
|
+
equatorial_coordinates
|
186
|
+
)
|
187
|
+
# If the sun doesn't rise or set on this day, we can't calculate twilight
|
188
|
+
return nil unless period_time
|
189
|
+
|
190
|
+
hour_angle_at_period = equatorial_coordinates
|
191
|
+
.compute_hour_angle(time: period_time, longitude: @observer.longitude)
|
192
|
+
|
193
|
+
term1 = zenith_angle.cos -
|
194
|
+
@observer.latitude.sin * equatorial_coordinates.declination.sin
|
195
|
+
term2 = @observer.latitude.cos * equatorial_coordinates.declination.cos
|
196
|
+
hour_angle_ratio_at_twilight = term1 / term2
|
197
|
+
|
198
|
+
# Check if twilight occurs at this location and date
|
199
|
+
return nil unless hour_angle_ratio_at_twilight.between?(-1, 1)
|
200
|
+
|
201
|
+
hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight)
|
202
|
+
time_sign = -1
|
203
|
+
|
204
|
+
if period_of_the_day == MORNING
|
205
|
+
hour_angle_at_twilight = Angle.from_degrees(
|
206
|
+
Constants::DEGREES_PER_CIRCLE - hour_angle_at_twilight.degrees
|
207
|
+
)
|
208
|
+
time_sign = 1
|
209
|
+
end
|
210
|
+
|
211
|
+
twilight_in_hours =
|
212
|
+
time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours *
|
213
|
+
GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE
|
214
|
+
twilight_in_seconds = time_sign *
|
215
|
+
twilight_in_hours *
|
216
|
+
Constants::SECONDS_PER_HOUR
|
217
|
+
|
218
|
+
(period_time + twilight_in_seconds).round
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class TwilightEvent
|
5
|
+
attr_reader :morning_civil_twilight_time,
|
6
|
+
:evening_civil_twilight_time,
|
7
|
+
:morning_nautical_twilight_time,
|
8
|
+
:evening_nautical_twilight_time,
|
9
|
+
:morning_astronomical_twilight_time,
|
10
|
+
:evening_astronomical_twilight_time
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
morning_civil_twilight_time: nil,
|
14
|
+
evening_civil_twilight_time: nil,
|
15
|
+
morning_nautical_twilight_time: nil,
|
16
|
+
evening_nautical_twilight_time: nil,
|
17
|
+
morning_astronomical_twilight_time: nil,
|
18
|
+
evening_astronomical_twilight_time: nil
|
19
|
+
)
|
20
|
+
@morning_civil_twilight_time = morning_civil_twilight_time
|
21
|
+
@evening_civil_twilight_time = evening_civil_twilight_time
|
22
|
+
@morning_nautical_twilight_time = morning_nautical_twilight_time
|
23
|
+
@evening_nautical_twilight_time = evening_nautical_twilight_time
|
24
|
+
@morning_astronomical_twilight_time = morning_astronomical_twilight_time
|
25
|
+
@evening_astronomical_twilight_time = evening_astronomical_twilight_time
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,121 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Astronoby
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
:evening_nautical_twilight_time,
|
27
|
-
:morning_astronomical_twilight_time,
|
28
|
-
:evening_astronomical_twilight_time
|
29
|
-
|
30
|
-
def initialize(observer:, sun:)
|
31
|
-
@observer = observer
|
32
|
-
@sun = sun
|
33
|
-
PERIODS_OF_THE_DAY.each do |period_of_the_day|
|
34
|
-
TWILIGHT_ANGLES.each do |twilight, _|
|
35
|
-
zenith_angle = TWILIGHT_ANGLES[twilight]
|
36
|
-
instance_variable_set(
|
37
|
-
:"@#{period_of_the_day}_#{twilight}_twilight_time",
|
38
|
-
compute(period_of_the_day, zenith_angle)
|
39
|
-
)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# @param period_of_the_day [Symbol] :morning or :evening
|
45
|
-
# @param zenith_angle [Angle] The zenith angle of the twilight
|
46
|
-
def time_for_zenith_angle(period_of_the_day:, zenith_angle:)
|
47
|
-
unless PERIODS_OF_THE_DAY.include?(period_of_the_day)
|
48
|
-
raise IncompatibleArgumentsError,
|
49
|
-
"Only #{PERIODS_OF_THE_DAY.join(" or ")} are allowed as period_of_the_day, got #{period_of_the_day}"
|
50
|
-
end
|
51
|
-
|
52
|
-
compute(period_of_the_day, zenith_angle)
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
# Source:
|
58
|
-
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
59
|
-
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
60
|
-
# Edition: Cambridge University Press
|
61
|
-
# Chapter: 50 - Twilight
|
62
|
-
def compute(period_of_the_day, zenith_angle)
|
63
|
-
period_time = if period_of_the_day == MORNING
|
64
|
-
observation_events.rising_time
|
65
|
-
else
|
66
|
-
observation_events.setting_time
|
67
|
-
end
|
68
|
-
|
69
|
-
hour_angle_at_period = equatorial_coordinates_at_midday
|
70
|
-
.compute_hour_angle(time: period_time, longitude: @observer.longitude)
|
71
|
-
|
72
|
-
term1 = zenith_angle.cos -
|
73
|
-
@observer.latitude.sin *
|
74
|
-
@equatorial_coordinates_at_midday.declination.sin
|
75
|
-
term2 = @observer.latitude.cos *
|
76
|
-
equatorial_coordinates_at_midday.declination.cos
|
77
|
-
hour_angle_ratio_at_twilight = term1 / term2
|
78
|
-
|
79
|
-
return unless hour_angle_ratio_at_twilight.between?(-1, 1)
|
80
|
-
|
81
|
-
hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight)
|
82
|
-
time_sign = -1
|
83
|
-
|
84
|
-
if period_of_the_day == MORNING
|
85
|
-
hour_angle_at_twilight = Angle.from_degrees(
|
86
|
-
Constants::DEGREES_PER_CIRCLE - hour_angle_at_twilight.degrees
|
87
|
-
)
|
88
|
-
time_sign = 1
|
89
|
-
end
|
90
|
-
|
91
|
-
twilight_in_hours =
|
92
|
-
time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours *
|
93
|
-
GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE
|
94
|
-
twilight_in_seconds = time_sign *
|
95
|
-
twilight_in_hours *
|
96
|
-
Constants::SECONDS_PER_HOUR
|
97
|
-
|
98
|
-
(period_time + twilight_in_seconds).round
|
99
|
-
end
|
100
|
-
|
101
|
-
def observation_events
|
102
|
-
@observation_events ||= @sun.observation_events(observer: @observer)
|
103
|
-
end
|
104
|
-
|
105
|
-
def midday
|
106
|
-
date = @sun.time.to_date
|
107
|
-
Time.utc(date.year, date.month, date.day, 12)
|
108
|
-
end
|
109
|
-
|
110
|
-
def sun_at_midday
|
111
|
-
Sun.new(time: midday)
|
112
|
-
end
|
113
|
-
|
114
|
-
def equatorial_coordinates_at_midday
|
115
|
-
@equatorial_coordinates_at_midday ||= sun_at_midday
|
116
|
-
.apparent_ecliptic_coordinates
|
117
|
-
.to_apparent_equatorial(epoch: Epoch.from_time(midday))
|
118
|
-
end
|
4
|
+
class TwilightEvents
|
5
|
+
attr_reader :morning_civil_twilight_times,
|
6
|
+
:evening_civil_twilight_times,
|
7
|
+
:morning_nautical_twilight_times,
|
8
|
+
:evening_nautical_twilight_times,
|
9
|
+
:morning_astronomical_twilight_times,
|
10
|
+
:evening_astronomical_twilight_times
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
morning_civil,
|
14
|
+
evening_civil,
|
15
|
+
morning_nautical,
|
16
|
+
evening_nautical,
|
17
|
+
morning_astronomical,
|
18
|
+
evening_astronomical
|
19
|
+
)
|
20
|
+
@morning_civil_twilight_times = morning_civil
|
21
|
+
@evening_civil_twilight_times = evening_civil
|
22
|
+
@morning_nautical_twilight_times = morning_nautical
|
23
|
+
@evening_nautical_twilight_times = evening_nautical
|
24
|
+
@morning_astronomical_twilight_times = morning_astronomical
|
25
|
+
@evening_astronomical_twilight_times = evening_astronomical
|
119
26
|
end
|
120
27
|
end
|
121
28
|
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module Astronoby
|
6
|
+
# Represents a specific instant in time using Terrestrial Time (TT) as its
|
7
|
+
# internal representation. This class provides conversions between different
|
8
|
+
# time scales commonly used in astronomy:
|
9
|
+
# - Terrestrial Time (TT)
|
10
|
+
# - International Atomic Time (TAI)
|
11
|
+
# - Universal Time Coordinated (UTC)
|
12
|
+
# - Greenwich Mean Sidereal Time (GMST)
|
13
|
+
#
|
14
|
+
# @example Create an instant from the current time
|
15
|
+
# instant = Astronoby::Instant.from_time(Time.now)
|
16
|
+
# instant.tai # Get International Atomic Time
|
17
|
+
#
|
18
|
+
# @example Create an instant from Terrestrial Time
|
19
|
+
# instant = Astronoby::Instant.from_terrestrial_time(2460000.5)
|
20
|
+
#
|
21
|
+
class Instant
|
22
|
+
include Comparable
|
23
|
+
|
24
|
+
# The adjustment value to align our noon-based Julian Date with the
|
25
|
+
# midnight-based epoch required by Ruby's `DateTime.jd` constructor.
|
26
|
+
# Our internal time values are standard astronomical Julian Dates, which
|
27
|
+
# start at noon. `DateTime.jd` expects a day that starts at the preceding
|
28
|
+
# midnight. This constant adds 0.5 days (12 hours) to make the conversion.
|
29
|
+
DATETIME_JD_EPOCH_ADJUSTMENT = 0.5
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Creates a new Instant from a Terrestrial Time value
|
33
|
+
#
|
34
|
+
# @param terrestrial_time [Numeric] the Terrestrial Time as a Julian Date
|
35
|
+
# @return [Astronoby::Instant] a new Instant object
|
36
|
+
# @raise [Astronoby::UnsupportedFormatError] if terrestrial_time is not
|
37
|
+
# numeric
|
38
|
+
def from_terrestrial_time(terrestrial_time)
|
39
|
+
new(terrestrial_time)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a new Instant from a Time object
|
43
|
+
#
|
44
|
+
# @param time [Time] a Time object to convert
|
45
|
+
# @return [Astronoby::Instant] a new Instant object
|
46
|
+
def from_time(time)
|
47
|
+
delta_t = Util::Time.terrestrial_universal_time_delta(time)
|
48
|
+
terrestrial_time = time.utc.to_datetime.ajd +
|
49
|
+
Rational(delta_t, Constants::SECONDS_PER_DAY)
|
50
|
+
from_terrestrial_time(terrestrial_time)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates a new Instant from a Julian Date in UTC
|
54
|
+
#
|
55
|
+
# @param julian_date [Numeric] the Julian Date in UTC
|
56
|
+
# @return [Astronoby::Instant] a new Instant object
|
57
|
+
def from_utc_julian_date(julian_date)
|
58
|
+
delta_t = Util::Time.terrestrial_universal_time_delta(julian_date)
|
59
|
+
terrestrial_time = julian_date +
|
60
|
+
Rational(delta_t, Constants::SECONDS_PER_DAY)
|
61
|
+
from_terrestrial_time(terrestrial_time)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :terrestrial_time
|
66
|
+
alias_method :tt, :terrestrial_time
|
67
|
+
alias_method :julian_date, :terrestrial_time
|
68
|
+
|
69
|
+
# Initialize a new Instant
|
70
|
+
#
|
71
|
+
# @param terrestrial_time [Numeric] the Terrestrial Time as Julian Date
|
72
|
+
# @raise [Astronoby::UnsupportedFormatError] if terrestrial_time is not
|
73
|
+
# numeric
|
74
|
+
def initialize(terrestrial_time)
|
75
|
+
unless terrestrial_time.is_a?(Numeric)
|
76
|
+
raise UnsupportedFormatError, "terrestrial_time must be a Numeric"
|
77
|
+
end
|
78
|
+
|
79
|
+
@terrestrial_time = terrestrial_time
|
80
|
+
freeze
|
81
|
+
end
|
82
|
+
|
83
|
+
# Calculate the time difference between two Instant objects
|
84
|
+
#
|
85
|
+
# @param other [Astronoby::Instant] another Instant to compare with
|
86
|
+
# @return [Numeric] the difference in days
|
87
|
+
def diff(other)
|
88
|
+
@terrestrial_time - other.terrestrial_time
|
89
|
+
end
|
90
|
+
|
91
|
+
# Convert to DateTime (UTC)
|
92
|
+
#
|
93
|
+
# @return [DateTime] the UTC time as DateTime
|
94
|
+
def to_datetime
|
95
|
+
DateTime.jd(
|
96
|
+
@terrestrial_time -
|
97
|
+
Rational(delta_t / Constants::SECONDS_PER_DAY) +
|
98
|
+
DATETIME_JD_EPOCH_ADJUSTMENT
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Convert to Date (UTC)
|
103
|
+
#
|
104
|
+
# @return [Date] the UTC date
|
105
|
+
def to_date
|
106
|
+
to_datetime.to_date
|
107
|
+
end
|
108
|
+
|
109
|
+
# Convert to Time (UTC)
|
110
|
+
#
|
111
|
+
# @return [Time] the UTC time
|
112
|
+
def to_time
|
113
|
+
to_datetime.to_time.utc
|
114
|
+
end
|
115
|
+
|
116
|
+
# Get the ΔT (Delta T) value for this instant
|
117
|
+
# ΔT is the difference between TT and UT1
|
118
|
+
#
|
119
|
+
# @return [Numeric] Delta T in seconds
|
120
|
+
def delta_t
|
121
|
+
Util::Time.terrestrial_universal_time_delta(@terrestrial_time)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Get the Greenwich Mean Sidereal Time
|
125
|
+
#
|
126
|
+
# @return [Numeric] the sidereal time in radians
|
127
|
+
def gmst
|
128
|
+
GreenwichSiderealTime.from_utc(to_time).time
|
129
|
+
end
|
130
|
+
|
131
|
+
# Get the International Atomic Time (TAI)
|
132
|
+
#
|
133
|
+
# @return [Numeric] TAI as Julian Date
|
134
|
+
def tai
|
135
|
+
@terrestrial_time -
|
136
|
+
Rational(Constants::TAI_TT_OFFSET, Constants::SECONDS_PER_DAY)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Get the Barycentric Dynamical Time (TDB)
|
140
|
+
# Note: Currently approximated as equal to TT
|
141
|
+
#
|
142
|
+
# @return [Numeric] TDB as Julian Date
|
143
|
+
def tdb
|
144
|
+
# This is technically false, there is a slight difference between TT and
|
145
|
+
# TDB. However, this difference is so small that currenly Astronoby
|
146
|
+
# doesn't support it and consider they are the same value.
|
147
|
+
@terrestrial_time
|
148
|
+
end
|
149
|
+
|
150
|
+
# Get the offset between TT and UTC for this instant
|
151
|
+
#
|
152
|
+
# @return [Numeric] the offset in days
|
153
|
+
def utc_offset
|
154
|
+
@terrestrial_time - to_time.utc.to_datetime.ajd
|
155
|
+
end
|
156
|
+
|
157
|
+
# Calculate hash value for the instant
|
158
|
+
#
|
159
|
+
# @return [Integer] hash value
|
160
|
+
def hash
|
161
|
+
[@terrestrial_time, self.class].hash
|
162
|
+
end
|
163
|
+
|
164
|
+
# Compare this instant with another
|
165
|
+
#
|
166
|
+
# @param other [Astronoby::Instant] another instant to compare with
|
167
|
+
# @return [Integer, nil] -1, 0, 1 for less than, equal to, greater than;
|
168
|
+
# nil if other is not an Instant
|
169
|
+
def <=>(other)
|
170
|
+
return unless other.is_a?(self.class)
|
171
|
+
|
172
|
+
@terrestrial_time <=> other.terrestrial_time
|
173
|
+
end
|
174
|
+
alias_method :eql?, :==
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
# @see https://en.wikipedia.org/wiki/Julian_day
|
5
|
+
# @see https://en.wikipedia.org/wiki/Epoch_(astronomy)
|
6
|
+
class JulianDate
|
7
|
+
# Starting year for Besselian epoch calculations
|
8
|
+
# @return [Integer] 1900
|
9
|
+
BESSELIAN_EPOCH_STARTING_YEAR = 1900
|
10
|
+
|
11
|
+
# Starting year for Julian epoch calculations
|
12
|
+
# @return [Integer] 2000
|
13
|
+
JULIAN_EPOCH_STARTING_YEAR = 2000
|
14
|
+
|
15
|
+
# Julian Date for Besselian epoch 1875.0
|
16
|
+
# @return [Float] 2405889.258550475
|
17
|
+
B1875 = 2405889.258550475
|
18
|
+
|
19
|
+
# Julian Date for Besselian epoch 1900.0
|
20
|
+
# @return [Float] 2415020.31352
|
21
|
+
B1900 = 2415020.31352
|
22
|
+
|
23
|
+
# Julian Date for Julian epoch 1950.0
|
24
|
+
# @return [Float] 2433282.5
|
25
|
+
J1950 = 2433282.5
|
26
|
+
|
27
|
+
# Julian Date for Julian epoch 2000.0 (current standard)
|
28
|
+
# @return [Float] 2451545.0
|
29
|
+
J2000 = 2451545.0
|
30
|
+
|
31
|
+
# Default epoch used by the library
|
32
|
+
# @return [Float] 2451545.0
|
33
|
+
DEFAULT_EPOCH = J2000
|
34
|
+
|
35
|
+
# Converts a Time object to Julian Date
|
36
|
+
#
|
37
|
+
# @param time [Time] the time to convert
|
38
|
+
# @return [Rational] the Julian Date
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# JulianDate.from_time(Time.utc(2000, 1, 1, 12, 0, 0))
|
42
|
+
# # => 2451545.0
|
43
|
+
def self.from_time(time)
|
44
|
+
time.to_datetime.ajd
|
45
|
+
end
|
46
|
+
|
47
|
+
# Converts a Julian year to Julian Date
|
48
|
+
#
|
49
|
+
# Uses the formula: JD = J2000 + 365.25 * (year - 2000)
|
50
|
+
#
|
51
|
+
# @param julian_year [Float] the Julian year
|
52
|
+
# @return [Float] the Julian Date
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# JulianDate.from_julian_year(2025.0)
|
56
|
+
# # => 2460676.25
|
57
|
+
def self.from_julian_year(julian_year)
|
58
|
+
J2000 + Constants::DAYS_PER_JULIAN_YEAR *
|
59
|
+
(julian_year - JULIAN_EPOCH_STARTING_YEAR)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Converts a Besselian year to Julian Date
|
63
|
+
#
|
64
|
+
# Uses the formula: JD = B1900 + 365.242198781 * (year - 1900)
|
65
|
+
# where 365.242198781 is the tropical year length at B1900.
|
66
|
+
#
|
67
|
+
# @param besselian_year [Float] the Besselian year
|
68
|
+
# @return [Float] the Julian Date
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# JulianDate.from_besselian_year(1875.0)
|
72
|
+
# # => 2405889.258550475
|
73
|
+
def self.from_besselian_year(besselian_year)
|
74
|
+
B1900 + Constants::TROPICAL_YEAR_AT_B1900 *
|
75
|
+
(besselian_year - BESSELIAN_EPOCH_STARTING_YEAR)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -1,32 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# TODO: This needs to be improved by receiving an instant instead of an Epoch
|
4
|
+
# as these coefficients work with TT (Terrestrial Time).
|
4
5
|
|
5
6
|
module Astronoby
|
6
7
|
class MeanObliquity
|
7
8
|
# Source:
|
8
9
|
# IAU resolution in 2006 in favor of the P03 astronomical model
|
9
|
-
#
|
10
|
+
# https://syrte.obspm.fr/iau2006/aa03_412_P03.pdf
|
10
11
|
|
11
|
-
EPOCH_OF_REFERENCE =
|
12
|
+
EPOCH_OF_REFERENCE = JulianDate::DEFAULT_EPOCH
|
12
13
|
OBLIQUITY_OF_REFERENCE = 23.4392794
|
13
14
|
|
14
|
-
def self.
|
15
|
-
return obliquity_of_reference if
|
15
|
+
def self.at(instant)
|
16
|
+
return obliquity_of_reference if instant.julian_date == EPOCH_OF_REFERENCE
|
16
17
|
|
17
|
-
t = (
|
18
|
+
t = Rational(
|
19
|
+
instant.julian_date - EPOCH_OF_REFERENCE,
|
20
|
+
Constants::DAYS_PER_JULIAN_CENTURY
|
21
|
+
)
|
22
|
+
|
23
|
+
epsilon0 = obliquity_of_reference_in_arcseconds
|
24
|
+
c1 = -46.836769
|
25
|
+
c2 = -0.0001831
|
26
|
+
c3 = 0.00200340
|
27
|
+
c4 = -0.000000576
|
28
|
+
c5 = -0.0000000434
|
18
29
|
|
19
|
-
Angle.
|
20
|
-
|
21
|
-
46.815 * t -
|
22
|
-
0.0006 * t * t +
|
23
|
-
0.00181 * t * t * t
|
24
|
-
) / Constants::SECONDS_PER_DEGREE
|
30
|
+
Angle.from_degree_arcseconds(
|
31
|
+
epsilon0 + t * (c1 + t * (c2 + t * (c3 + t * (c4 + t * c5))))
|
25
32
|
)
|
26
33
|
end
|
27
34
|
|
28
35
|
def self.obliquity_of_reference
|
29
|
-
Angle.
|
36
|
+
Angle.from_degree_arcseconds(obliquity_of_reference_in_arcseconds)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.obliquity_of_reference_in_arcseconds
|
40
|
+
84381.406
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|