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,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Topocentric reference frame. Represents a body's position as seen from a
|
|
5
|
+
# specific observer on Earth's surface, accounting for the observer's
|
|
6
|
+
# geocentric position and Earth rotation.
|
|
4
7
|
class Topocentric < ReferenceFrame
|
|
8
|
+
# Builds a topocentric frame from an apparent frame and observer.
|
|
9
|
+
#
|
|
10
|
+
# @param apparent [Astronoby::Apparent] the apparent frame
|
|
11
|
+
# @param observer [Astronoby::Observer] the observer
|
|
12
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
13
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
14
|
+
# @return [Astronoby::Topocentric] a new topocentric frame
|
|
5
15
|
def self.build_from_apparent(
|
|
6
16
|
apparent:,
|
|
7
17
|
observer:,
|
|
@@ -24,17 +34,23 @@ module Astronoby
|
|
|
24
34
|
position: position,
|
|
25
35
|
velocity: velocity,
|
|
26
36
|
instant: instant,
|
|
27
|
-
|
|
37
|
+
center: Center.topocentric(observer),
|
|
28
38
|
target_body: target_body,
|
|
29
39
|
observer: observer
|
|
30
40
|
)
|
|
31
41
|
end
|
|
32
42
|
|
|
43
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
44
|
+
# @param velocity [Astronoby::Vector<Astronoby::Velocity>] velocity vector
|
|
45
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
46
|
+
# @param center [Astronoby::Center] the center of the frame
|
|
47
|
+
# @param target_body [Astronoby::Body, nil] the target body
|
|
48
|
+
# @param observer [Astronoby::Observer] the observer
|
|
33
49
|
def initialize(
|
|
34
50
|
position:,
|
|
35
51
|
velocity:,
|
|
36
52
|
instant:,
|
|
37
|
-
|
|
53
|
+
center:,
|
|
38
54
|
target_body:,
|
|
39
55
|
observer:
|
|
40
56
|
)
|
|
@@ -42,20 +58,36 @@ module Astronoby
|
|
|
42
58
|
position: position,
|
|
43
59
|
velocity: velocity,
|
|
44
60
|
instant: instant,
|
|
45
|
-
|
|
61
|
+
center: center,
|
|
46
62
|
target_body: target_body
|
|
47
63
|
)
|
|
48
64
|
@observer = observer
|
|
49
65
|
end
|
|
50
66
|
|
|
67
|
+
# @return [Numeric] Julian Date epoch of the frame's coordinates, which
|
|
68
|
+
# are referred to the true equator and equinox of date
|
|
69
|
+
def epoch
|
|
70
|
+
instant.tt
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates at the
|
|
74
|
+
# current instant (true ecliptic and equinox of date)
|
|
51
75
|
def ecliptic
|
|
52
76
|
@ecliptic ||= begin
|
|
53
77
|
return Coordinates::Ecliptic.zero if distance.zero?
|
|
54
78
|
|
|
55
|
-
equatorial.to_ecliptic(
|
|
79
|
+
equatorial.to_ecliptic(
|
|
80
|
+
instant: @instant,
|
|
81
|
+
obliquity: TrueObliquity.at(@instant)
|
|
82
|
+
)
|
|
56
83
|
end
|
|
57
84
|
end
|
|
58
85
|
|
|
86
|
+
# Converts to horizontal coordinates (azimuth/altitude).
|
|
87
|
+
#
|
|
88
|
+
# @param refraction [Boolean] whether to apply atmospheric refraction
|
|
89
|
+
# correction (default: false)
|
|
90
|
+
# @return [Astronoby::Coordinates::Horizontal] horizontal coordinates
|
|
59
91
|
def horizontal(refraction: false)
|
|
60
92
|
horizontal = equatorial.to_horizontal(
|
|
61
93
|
time: @instant.to_time,
|
data/lib/astronoby/refraction.rb
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Computes atmospheric refraction corrections for horizontal coordinates.
|
|
5
|
+
#
|
|
6
|
+
# Source:
|
|
7
|
+
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
8
|
+
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
9
|
+
# Edition: Cambridge University Press
|
|
10
|
+
# Chapter: 37 - Refraction
|
|
4
11
|
class Refraction
|
|
5
12
|
LOW_ALTITUDE_BODY_ANGLE = Angle.from_degrees(15)
|
|
6
13
|
ZENITH = Angle.from_degrees(90)
|
|
7
14
|
|
|
15
|
+
# Computes the refraction angle for the given horizontal coordinates.
|
|
16
|
+
#
|
|
17
|
+
# @param coordinates [Astronoby::Coordinates::Horizontal] horizontal
|
|
18
|
+
# coordinates
|
|
19
|
+
# @return [Astronoby::Angle] the refraction angle
|
|
8
20
|
def self.angle(coordinates:)
|
|
9
21
|
new(coordinates).refraction_angle
|
|
10
22
|
end
|
|
11
23
|
|
|
24
|
+
# Returns horizontal coordinates corrected for atmospheric refraction.
|
|
25
|
+
#
|
|
26
|
+
# @param coordinates [Astronoby::Coordinates::Horizontal] horizontal
|
|
27
|
+
# coordinates
|
|
28
|
+
# @return [Astronoby::Coordinates::Horizontal] corrected coordinates
|
|
12
29
|
def self.correct_horizontal_coordinates(coordinates:)
|
|
13
30
|
new(coordinates).refract
|
|
14
31
|
end
|
|
15
32
|
|
|
33
|
+
# @param coordinates [Astronoby::Coordinates::Horizontal] horizontal
|
|
34
|
+
# coordinates
|
|
16
35
|
def initialize(coordinates)
|
|
17
36
|
@coordinates = coordinates
|
|
18
37
|
end
|
|
19
38
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
# Edition: Cambridge University Press
|
|
24
|
-
# Chapter: 37 - Refraction
|
|
39
|
+
# Returns horizontal coordinates with refraction applied to the altitude.
|
|
40
|
+
#
|
|
41
|
+
# @return [Astronoby::Coordinates::Horizontal] corrected coordinates
|
|
25
42
|
def refract
|
|
26
43
|
Coordinates::Horizontal.new(
|
|
27
44
|
azimuth: @coordinates.azimuth,
|
|
@@ -30,6 +47,10 @@ module Astronoby
|
|
|
30
47
|
)
|
|
31
48
|
end
|
|
32
49
|
|
|
50
|
+
# Computes the refraction angle based on the observer's atmospheric
|
|
51
|
+
# conditions and the body's altitude.
|
|
52
|
+
#
|
|
53
|
+
# @return [Astronoby::Angle] the refraction angle
|
|
33
54
|
def refraction_angle
|
|
34
55
|
if @coordinates.altitude > LOW_ALTITUDE_BODY_ANGLE
|
|
35
56
|
high_altitude_angle
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class RootFinder
|
|
5
|
+
MIN_SAMPLES_PER_PERIOD = 20
|
|
6
|
+
BISECTION_TOLERANCE_DAYS = 1e-8
|
|
7
|
+
|
|
8
|
+
# @param value_at [#call] callable mapping a Julian Date (Terrestrial Time)
|
|
9
|
+
# to a Float
|
|
10
|
+
# @param period [Float] the characteristic period of the quantity in days
|
|
11
|
+
# @param samples_per_period [Integer] number of samples per period
|
|
12
|
+
def initialize(value_at:, period:, samples_per_period: 60)
|
|
13
|
+
@value_at = value_at
|
|
14
|
+
@period = period
|
|
15
|
+
@samples_per_period = samples_per_period
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param start_jd [Float] start time in Julian Date (Terrestrial Time)
|
|
19
|
+
# @param end_jd [Float] end time in Julian Date (Terrestrial Time)
|
|
20
|
+
# @param accept [#call, nil] optional predicate on a located root (a Julian
|
|
21
|
+
# Date)
|
|
22
|
+
# @return [Array<Float>] root times in Julian Date (Terrestrial Time),
|
|
23
|
+
# sorted by time
|
|
24
|
+
def roots(start_jd, end_jd, accept: nil)
|
|
25
|
+
samples = collect_samples(start_jd, end_jd)
|
|
26
|
+
located = (
|
|
27
|
+
samples_on_root(samples) +
|
|
28
|
+
find_brackets(samples).map { |a, b, f_a| bisect(a, b, f_a) }
|
|
29
|
+
).uniq.sort
|
|
30
|
+
located.select { |jd| accept.nil? || accept.call(jd) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def samples_on_root(samples)
|
|
36
|
+
samples.filter_map { |sample| sample[:jd] if sample[:value].zero? }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_brackets(samples)
|
|
40
|
+
(0...samples.length - 1).filter_map do |i|
|
|
41
|
+
current = samples[i]
|
|
42
|
+
following = samples[i + 1]
|
|
43
|
+
|
|
44
|
+
if sign_change?(current, following)
|
|
45
|
+
[current[:jd], following[:jd], current[:value]]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def sign_change?(current, following)
|
|
51
|
+
current[:value] * following[:value] < 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def collect_samples(start_jd, end_jd)
|
|
55
|
+
duration = end_jd - start_jd
|
|
56
|
+
sample_count = sample_count_for(duration)
|
|
57
|
+
step = duration / sample_count
|
|
58
|
+
|
|
59
|
+
(0..sample_count).map do |i|
|
|
60
|
+
jd = start_jd + (i * step)
|
|
61
|
+
{jd: jd, value: @value_at.call(jd)}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def sample_count_for(duration)
|
|
66
|
+
periods_in_range = duration / @period
|
|
67
|
+
base_samples = (periods_in_range * @samples_per_period).to_i
|
|
68
|
+
[base_samples, MIN_SAMPLES_PER_PERIOD].max
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def bisect(a, b, f_a)
|
|
72
|
+
while (b - a).abs > BISECTION_TOLERANCE_DAYS
|
|
73
|
+
midpoint = (a + b) / 2
|
|
74
|
+
break if midpoint == a || midpoint == b
|
|
75
|
+
|
|
76
|
+
f_midpoint = @value_at.call(midpoint)
|
|
77
|
+
return midpoint if f_midpoint.zero?
|
|
78
|
+
|
|
79
|
+
if f_a.negative? == f_midpoint.negative?
|
|
80
|
+
a = midpoint
|
|
81
|
+
f_a = f_midpoint
|
|
82
|
+
else
|
|
83
|
+
b = midpoint
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
(a + b) / 2
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "matrix"
|
|
4
|
+
|
|
5
|
+
module Astronoby
|
|
6
|
+
# Builds elementary rotation matrices about the coordinate axes from an
|
|
7
|
+
# +Astronoby::Angle+.
|
|
8
|
+
#
|
|
9
|
+
# These use the passive (frame) convention: the matrix expresses a fixed
|
|
10
|
+
# vector in a frame rotated by +angle+ about the axis. This is the convention
|
|
11
|
+
# used throughout the reference-frame chain (precession, nutation, body
|
|
12
|
+
# orientation).
|
|
13
|
+
module Rotation
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# @param angle [Astronoby::Angle] the rotation angle
|
|
17
|
+
# @return [Matrix] rotation about the x-axis
|
|
18
|
+
def about_x(angle)
|
|
19
|
+
cosine, sine = angle.cos, angle.sin
|
|
20
|
+
Matrix[
|
|
21
|
+
[1, 0, 0],
|
|
22
|
+
[0, cosine, sine],
|
|
23
|
+
[0, -sine, cosine]
|
|
24
|
+
]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param angle [Astronoby::Angle] the rotation angle
|
|
28
|
+
# @return [Matrix] rotation about the y-axis
|
|
29
|
+
def about_y(angle)
|
|
30
|
+
cosine, sine = angle.cos, angle.sin
|
|
31
|
+
Matrix[
|
|
32
|
+
[cosine, 0, -sine],
|
|
33
|
+
[0, 1, 0],
|
|
34
|
+
[sine, 0, cosine]
|
|
35
|
+
]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param angle [Astronoby::Angle] the rotation angle
|
|
39
|
+
# @return [Matrix] rotation about the z-axis
|
|
40
|
+
def about_z(angle)
|
|
41
|
+
cosine, sine = angle.cos, angle.sin
|
|
42
|
+
Matrix[
|
|
43
|
+
[cosine, sine, 0],
|
|
44
|
+
[-sine, cosine, 0],
|
|
45
|
+
[0, 0, 1]
|
|
46
|
+
]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Greenwich Apparent Sidereal Time (GAST). Derived from GMST by applying
|
|
5
|
+
# the equation of the equinoxes (nutation correction).
|
|
4
6
|
class GreenwichApparentSiderealTime < GreenwichSiderealTime
|
|
7
|
+
# Creates GAST from UTC by computing GMST and applying the equation
|
|
8
|
+
# of the equinoxes.
|
|
9
|
+
#
|
|
10
|
+
# @param utc [Time] the UTC time
|
|
11
|
+
# @return [Astronoby::GreenwichApparentSiderealTime]
|
|
5
12
|
def self.from_utc(utc)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
Astronoby.cache.fetch(CacheKey.generate_for_utc(:gast, utc)) do
|
|
14
|
+
gmst = GreenwichMeanSiderealTime.from_utc(utc)
|
|
15
|
+
instant = Instant.from_time(utc)
|
|
16
|
+
nutation = Nutation.new(instant: instant)
|
|
17
|
+
mean_obliquity = MeanObliquity.at(instant)
|
|
10
18
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
equation_of_equinoxes = nutation.nutation_in_longitude.hours *
|
|
20
|
+
mean_obliquity.cos
|
|
21
|
+
gast_time = normalize_time(gmst.time + equation_of_equinoxes)
|
|
14
22
|
|
|
15
|
-
|
|
23
|
+
new(date: gmst.date, time: gast_time)
|
|
24
|
+
end
|
|
16
25
|
end
|
|
17
26
|
|
|
27
|
+
# @param date [Date] the calendar date
|
|
28
|
+
# @param time [Numeric] GAST in hours
|
|
18
29
|
def initialize(date:, time:)
|
|
19
30
|
super(date: date, time: time, type: APPARENT)
|
|
20
31
|
end
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "iers"
|
|
4
|
+
|
|
3
5
|
module Astronoby
|
|
6
|
+
# Greenwich Mean Sidereal Time (GMST). Computed from UTC using the IERS
|
|
7
|
+
# Conventions 2010 ERA-based expression, with a polynomial fallback for
|
|
8
|
+
# dates outside the IERS EOP data range.
|
|
4
9
|
class GreenwichMeanSiderealTime < GreenwichSiderealTime
|
|
5
10
|
JULIAN_CENTURIES_EXPONENTS = [
|
|
6
11
|
6.697374558,
|
|
@@ -10,13 +15,43 @@ module Astronoby
|
|
|
10
15
|
|
|
11
16
|
SIDEREAL_MINUTE_IN_UT_MINUTE = 0.9972695663
|
|
12
17
|
|
|
18
|
+
UT_TO_SIDEREAL_RATIO = 1.002737909
|
|
19
|
+
|
|
20
|
+
# Creates GMST from UTC using the IERS Conventions 2010 ERA-based
|
|
21
|
+
# expression.
|
|
22
|
+
#
|
|
23
|
+
# Source:
|
|
24
|
+
# Title: IERS Conventions (2010)
|
|
25
|
+
# Chapter: 5.5.7 - ERA based expressions for Greenwich Sidereal Time
|
|
26
|
+
#
|
|
27
|
+
# @param utc [Time] the UTC time
|
|
28
|
+
# @return [Astronoby::GreenwichMeanSiderealTime]
|
|
29
|
+
def self.from_utc(utc)
|
|
30
|
+
Astronoby.cache.fetch(CacheKey.generate_for_utc(:gmst, utc)) do
|
|
31
|
+
date = utc.to_date
|
|
32
|
+
gmst_hours = begin
|
|
33
|
+
gmst_radians = IERS::GMST.at(utc)
|
|
34
|
+
gmst_radians * 12.0 / Math::PI
|
|
35
|
+
rescue IERS::OutOfRangeError
|
|
36
|
+
from_utc_polynomial(utc)
|
|
37
|
+
end
|
|
38
|
+
gmst_hours = normalize_time(gmst_hours)
|
|
39
|
+
|
|
40
|
+
new(date: date, time: gmst_hours)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Polynomial fallback for dates outside the IERS EOP data range.
|
|
45
|
+
#
|
|
13
46
|
# Source:
|
|
14
47
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
15
48
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
16
49
|
# Edition: Cambridge University Press
|
|
17
50
|
# Chapter: 12 - Conversion of UT to Greenwich sidereal time (GST)
|
|
18
|
-
|
|
19
|
-
|
|
51
|
+
#
|
|
52
|
+
# @param utc [Time] the UTC time
|
|
53
|
+
# @return [Numeric] GMST in hours
|
|
54
|
+
def self.from_utc_polynomial(utc)
|
|
20
55
|
julian_day = utc.to_date.ajd
|
|
21
56
|
t = (julian_day - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
|
|
22
57
|
t0 = (
|
|
@@ -29,20 +64,24 @@ module Astronoby
|
|
|
29
64
|
utc.min / Constants::MINUTES_PER_HOUR +
|
|
30
65
|
(utc.sec + utc.subsec) / Constants::SECONDS_PER_HOUR
|
|
31
66
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
new(date: date, time: gmst)
|
|
67
|
+
UT_TO_SIDEREAL_RATIO * ut_in_hours + t0
|
|
35
68
|
end
|
|
36
69
|
|
|
70
|
+
# @param date [Date] the calendar date
|
|
71
|
+
# @param time [Numeric] GMST in hours
|
|
37
72
|
def initialize(date:, time:)
|
|
38
73
|
super(date: date, time: time, type: MEAN)
|
|
39
74
|
end
|
|
40
75
|
|
|
76
|
+
# Converts GMST back to UTC.
|
|
77
|
+
#
|
|
41
78
|
# Source:
|
|
42
79
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
43
80
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
44
81
|
# Edition: Cambridge University Press
|
|
45
82
|
# Chapter: 13 - Conversion of GST to UT
|
|
83
|
+
#
|
|
84
|
+
# @return [Time] the UTC time
|
|
46
85
|
def to_utc
|
|
47
86
|
date = @date
|
|
48
87
|
julian_day = @date.ajd
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Greenwich Sidereal Time base class. Dispatches to mean or apparent
|
|
5
|
+
# subclasses.
|
|
4
6
|
class GreenwichSiderealTime < SiderealTime
|
|
7
|
+
# Creates a Greenwich Sidereal Time from UTC.
|
|
8
|
+
#
|
|
9
|
+
# @param utc [Time] the UTC time
|
|
10
|
+
# @param type [Symbol] :mean or :apparent
|
|
11
|
+
# @return [Astronoby::GreenwichMeanSiderealTime,
|
|
12
|
+
# Astronoby::GreenwichApparentSiderealTime]
|
|
5
13
|
def self.from_utc(utc, type: MEAN)
|
|
6
14
|
validate_type!(type)
|
|
7
15
|
case type
|
|
@@ -12,14 +20,22 @@ module Astronoby
|
|
|
12
20
|
end
|
|
13
21
|
end
|
|
14
22
|
|
|
23
|
+
# @param utc [Time] the UTC time
|
|
24
|
+
# @return [Astronoby::GreenwichMeanSiderealTime]
|
|
15
25
|
def self.mean_from_utc(utc)
|
|
16
26
|
GreenwichMeanSiderealTime.from_utc(utc)
|
|
17
27
|
end
|
|
18
28
|
|
|
29
|
+
# @param utc [Time] the UTC time
|
|
30
|
+
# @return [Astronoby::GreenwichApparentSiderealTime]
|
|
19
31
|
def self.apparent_from_utc(utc)
|
|
20
32
|
GreenwichApparentSiderealTime.from_utc(utc)
|
|
21
33
|
end
|
|
22
34
|
|
|
35
|
+
# Converts to UTC.
|
|
36
|
+
#
|
|
37
|
+
# @return [Time] the UTC time
|
|
38
|
+
# @raise [NotImplementedError] if this is apparent sidereal time
|
|
23
39
|
def to_utc
|
|
24
40
|
unless mean?
|
|
25
41
|
raise NotImplementedError,
|
|
@@ -30,6 +46,11 @@ module Astronoby
|
|
|
30
46
|
gmst.to_utc
|
|
31
47
|
end
|
|
32
48
|
|
|
49
|
+
# Converts to Local Sidereal Time for a given longitude.
|
|
50
|
+
#
|
|
51
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
52
|
+
# @return [Astronoby::LocalMeanSiderealTime,
|
|
53
|
+
# Astronoby::LocalApparentSiderealTime]
|
|
33
54
|
def to_lst(longitude:)
|
|
34
55
|
LocalSiderealTime.from_gst(gst: self, longitude: longitude)
|
|
35
56
|
end
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Local Apparent Sidereal Time (LAST). Derived from GAST by adding the
|
|
5
|
+
# observer's longitude.
|
|
4
6
|
class LocalApparentSiderealTime < LocalSiderealTime
|
|
7
|
+
# Creates LAST from a Greenwich Apparent Sidereal Time and longitude.
|
|
8
|
+
#
|
|
5
9
|
# Source:
|
|
6
10
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
7
11
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
8
12
|
# Edition: Cambridge University Press
|
|
9
13
|
# Chapter: 14 - Local sidereal time (LST)
|
|
14
|
+
#
|
|
15
|
+
# @param gst [Astronoby::GreenwichApparentSiderealTime] GAST
|
|
16
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
17
|
+
# @return [Astronoby::LocalApparentSiderealTime]
|
|
18
|
+
# @raise [ArgumentError] if gst is not apparent sidereal time
|
|
10
19
|
def self.from_gst(gst:, longitude:)
|
|
11
20
|
unless gst.apparent?
|
|
12
21
|
raise ArgumentError, "GST must be apparent sidereal time"
|
|
@@ -18,20 +27,32 @@ module Astronoby
|
|
|
18
27
|
new(date: date, time: time, longitude: longitude)
|
|
19
28
|
end
|
|
20
29
|
|
|
30
|
+
# Creates LAST from UTC and longitude.
|
|
31
|
+
#
|
|
32
|
+
# @param utc [Time] the UTC time
|
|
33
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
34
|
+
# @return [Astronoby::LocalApparentSiderealTime]
|
|
21
35
|
def self.from_utc(utc, longitude:)
|
|
22
36
|
gast = GreenwichApparentSiderealTime.from_utc(utc)
|
|
23
37
|
from_gst(gst: gast, longitude: longitude)
|
|
24
38
|
end
|
|
25
39
|
|
|
40
|
+
# @param date [Date] the calendar date
|
|
41
|
+
# @param time [Numeric] LAST in hours
|
|
42
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
26
43
|
def initialize(date:, time:, longitude:)
|
|
27
44
|
super(date: date, time: time, longitude: longitude, type: APPARENT)
|
|
28
45
|
end
|
|
29
46
|
|
|
47
|
+
# Converts to Greenwich Apparent Sidereal Time.
|
|
48
|
+
#
|
|
30
49
|
# Source:
|
|
31
50
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
32
51
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
33
52
|
# Edition: Cambridge University Press
|
|
34
53
|
# Chapter: 15 - Converting LST to GST
|
|
54
|
+
#
|
|
55
|
+
# @return [Astronoby::GreenwichApparentSiderealTime]
|
|
35
56
|
def to_gst
|
|
36
57
|
date = @date
|
|
37
58
|
time = normalize_time(@time - @longitude.hours)
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Local Mean Sidereal Time (LMST). Derived from GMST by adding the
|
|
5
|
+
# observer's longitude.
|
|
4
6
|
class LocalMeanSiderealTime < LocalSiderealTime
|
|
7
|
+
# Creates LMST from a Greenwich Mean Sidereal Time and longitude.
|
|
8
|
+
#
|
|
5
9
|
# Source:
|
|
6
10
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
7
11
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
8
12
|
# Edition: Cambridge University Press
|
|
9
13
|
# Chapter: 14 - Local sidereal time (LST)
|
|
14
|
+
#
|
|
15
|
+
# @param gst [Astronoby::GreenwichMeanSiderealTime] GMST
|
|
16
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
17
|
+
# @return [Astronoby::LocalMeanSiderealTime]
|
|
18
|
+
# @raise [ArgumentError] if gst is not mean sidereal time
|
|
10
19
|
def self.from_gst(gst:, longitude:)
|
|
11
20
|
unless gst.mean?
|
|
12
21
|
raise ArgumentError, "GST must be mean sidereal time"
|
|
@@ -18,20 +27,32 @@ module Astronoby
|
|
|
18
27
|
new(date: date, time: time, longitude: longitude)
|
|
19
28
|
end
|
|
20
29
|
|
|
30
|
+
# Creates LMST from UTC and longitude.
|
|
31
|
+
#
|
|
32
|
+
# @param utc [Time] the UTC time
|
|
33
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
34
|
+
# @return [Astronoby::LocalMeanSiderealTime]
|
|
21
35
|
def self.from_utc(utc, longitude:)
|
|
22
36
|
gmst = GreenwichMeanSiderealTime.from_utc(utc)
|
|
23
37
|
from_gst(gst: gmst, longitude: longitude)
|
|
24
38
|
end
|
|
25
39
|
|
|
40
|
+
# @param date [Date] the calendar date
|
|
41
|
+
# @param time [Numeric] LMST in hours
|
|
42
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
26
43
|
def initialize(date:, time:, longitude:)
|
|
27
44
|
super(date: date, time: time, longitude: longitude, type: MEAN)
|
|
28
45
|
end
|
|
29
46
|
|
|
47
|
+
# Converts to Greenwich Mean Sidereal Time.
|
|
48
|
+
#
|
|
30
49
|
# Source:
|
|
31
50
|
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
|
32
51
|
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
|
33
52
|
# Edition: Cambridge University Press
|
|
34
53
|
# Chapter: 15 - Converting LST to GST
|
|
54
|
+
#
|
|
55
|
+
# @return [Astronoby::GreenwichMeanSiderealTime]
|
|
35
56
|
def to_gst
|
|
36
57
|
date = @date
|
|
37
58
|
time = normalize_time(@time - @longitude.hours)
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Local Sidereal Time base class. Dispatches to mean or apparent subclasses
|
|
5
|
+
# based on the type of the source GST.
|
|
4
6
|
class LocalSiderealTime < SiderealTime
|
|
7
|
+
# @return [Astronoby::Angle] the observer's longitude
|
|
5
8
|
attr_reader :longitude
|
|
6
9
|
|
|
10
|
+
# Creates an LST from a Greenwich Sidereal Time and longitude.
|
|
11
|
+
#
|
|
12
|
+
# @param gst [Astronoby::GreenwichSiderealTime] Greenwich Sidereal Time
|
|
13
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
14
|
+
# @return [Astronoby::LocalMeanSiderealTime,
|
|
15
|
+
# Astronoby::LocalApparentSiderealTime]
|
|
7
16
|
def self.from_gst(gst:, longitude:)
|
|
8
17
|
case gst.type
|
|
9
18
|
when MEAN
|
|
@@ -13,6 +22,13 @@ module Astronoby
|
|
|
13
22
|
end
|
|
14
23
|
end
|
|
15
24
|
|
|
25
|
+
# Creates an LST from UTC and longitude.
|
|
26
|
+
#
|
|
27
|
+
# @param utc [Time] the UTC time
|
|
28
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
29
|
+
# @param type [Symbol] :mean or :apparent
|
|
30
|
+
# @return [Astronoby::LocalMeanSiderealTime,
|
|
31
|
+
# Astronoby::LocalApparentSiderealTime]
|
|
16
32
|
def self.from_utc(utc, longitude:, type: MEAN)
|
|
17
33
|
validate_type!(type)
|
|
18
34
|
case type
|
|
@@ -23,11 +39,19 @@ module Astronoby
|
|
|
23
39
|
end
|
|
24
40
|
end
|
|
25
41
|
|
|
42
|
+
# @param date [Date] the calendar date
|
|
43
|
+
# @param time [Numeric] the sidereal time in hours
|
|
44
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
45
|
+
# @param type [Symbol] :mean or :apparent
|
|
26
46
|
def initialize(date:, time:, longitude:, type: MEAN)
|
|
27
47
|
super(date: date, time: time, type: type)
|
|
28
48
|
@longitude = longitude
|
|
29
49
|
end
|
|
30
50
|
|
|
51
|
+
# Converts to Greenwich Sidereal Time.
|
|
52
|
+
#
|
|
53
|
+
# @return [Astronoby::GreenwichMeanSiderealTime,
|
|
54
|
+
# Astronoby::GreenwichApparentSiderealTime]
|
|
31
55
|
def to_gst
|
|
32
56
|
case @type
|
|
33
57
|
when MEAN
|
|
@@ -1,40 +1,62 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Base class for sidereal time representations. Sidereal time measures the
|
|
5
|
+
# rotation of the Earth relative to the vernal equinox.
|
|
4
6
|
class SiderealTime
|
|
5
7
|
TYPES = [
|
|
6
8
|
MEAN = :mean,
|
|
7
9
|
APPARENT = :apparent
|
|
8
10
|
].freeze
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
# @return [Date] the calendar date
|
|
13
|
+
attr_reader :date
|
|
11
14
|
|
|
15
|
+
# @return [Numeric] the sidereal time in hours
|
|
16
|
+
attr_reader :time
|
|
17
|
+
|
|
18
|
+
# @return [Symbol] :mean or :apparent
|
|
19
|
+
attr_reader :type
|
|
20
|
+
|
|
21
|
+
# Normalizes a time value to the range [0, 24) hours.
|
|
22
|
+
#
|
|
23
|
+
# @param time [Numeric] time in hours
|
|
24
|
+
# @return [Numeric] normalized time in hours
|
|
12
25
|
def self.normalize_time(time)
|
|
13
26
|
time += Constants::HOURS_PER_DAY if time.negative?
|
|
14
27
|
time -= Constants::HOURS_PER_DAY if time > Constants::HOURS_PER_DAY
|
|
15
28
|
time
|
|
16
29
|
end
|
|
17
30
|
|
|
31
|
+
# @param type [Symbol] :mean or :apparent
|
|
32
|
+
# @raise [ArgumentError] if type is invalid
|
|
18
33
|
def self.validate_type!(type)
|
|
19
34
|
unless TYPES.include?(type)
|
|
20
35
|
raise ArgumentError, "Invalid type: #{type}. Must be one of #{TYPES}"
|
|
21
36
|
end
|
|
22
37
|
end
|
|
23
38
|
|
|
39
|
+
# @param date [Date] the calendar date
|
|
40
|
+
# @param time [Numeric] the sidereal time in hours
|
|
41
|
+
# @param type [Symbol] :mean or :apparent
|
|
24
42
|
def initialize(date:, time:, type: MEAN)
|
|
25
43
|
@date = date
|
|
26
44
|
@time = time
|
|
27
45
|
@type = type
|
|
28
46
|
end
|
|
29
47
|
|
|
48
|
+
# @return [Boolean] true if this is mean sidereal time
|
|
30
49
|
def mean?
|
|
31
50
|
@type == MEAN
|
|
32
51
|
end
|
|
33
52
|
|
|
53
|
+
# @return [Boolean] true if this is apparent sidereal time
|
|
34
54
|
def apparent?
|
|
35
55
|
@type == APPARENT
|
|
36
56
|
end
|
|
37
57
|
|
|
58
|
+
# @param time [Numeric] time in hours
|
|
59
|
+
# @return [Numeric] normalized time in hours
|
|
38
60
|
def normalize_time(time)
|
|
39
61
|
self.class.normalize_time(time)
|
|
40
62
|
end
|