astronoby 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +159 -0
- data/README.md +12 -5
- data/UPGRADING.md +109 -0
- data/docs/README.md +109 -16
- data/docs/angles.md +2 -1
- data/docs/configuration.md +20 -17
- data/docs/coordinates.md +73 -13
- data/docs/deep_sky_bodies.md +101 -0
- data/docs/ephem.md +6 -3
- data/docs/equinoxes_solstices_times.md +4 -3
- data/docs/glossary.md +97 -1
- data/docs/iers.md +40 -0
- data/docs/instant.md +21 -16
- data/docs/lunar_eclipses.md +93 -0
- data/docs/lunar_observation.md +87 -0
- data/docs/moon_phases.md +5 -2
- data/docs/observer.md +21 -7
- data/docs/planetary_phenomena.md +78 -0
- data/docs/reference_frames.md +193 -35
- data/docs/rise_transit_set_times.md +10 -8
- data/docs/{celestial_bodies.md → solar_system_bodies.md} +27 -5
- data/docs/twilight_times.md +25 -21
- data/lib/astronoby/angle.rb +69 -4
- data/lib/astronoby/angles/dms.rb +18 -1
- data/lib/astronoby/angles/hms.rb +14 -1
- data/lib/astronoby/angular_velocity.rb +97 -0
- data/lib/astronoby/bodies/deep_sky_object.rb +49 -0
- data/lib/astronoby/bodies/deep_sky_object_position.rb +142 -0
- data/lib/astronoby/bodies/earth.rb +9 -42
- data/lib/astronoby/bodies/jupiter.rb +10 -0
- data/lib/astronoby/bodies/mars.rb +10 -0
- data/lib/astronoby/bodies/mercury.rb +10 -0
- data/lib/astronoby/bodies/moon.rb +162 -15
- data/lib/astronoby/bodies/neptune.rb +10 -0
- data/lib/astronoby/bodies/saturn.rb +10 -0
- data/lib/astronoby/bodies/solar_system_body.rb +257 -53
- data/lib/astronoby/bodies/sun.rb +79 -4
- data/lib/astronoby/bodies/uranus.rb +10 -0
- data/lib/astronoby/bodies/venus.rb +10 -0
- data/lib/astronoby/body.rb +6 -0
- data/lib/astronoby/cache.rb +1 -0
- data/lib/astronoby/center.rb +84 -0
- data/lib/astronoby/constants.rb +7 -2
- data/lib/astronoby/constellation.rb +9 -1
- data/lib/astronoby/coordinates/ecliptic.rb +10 -1
- data/lib/astronoby/coordinates/equatorial.rb +66 -13
- data/lib/astronoby/coordinates/geodetic.rb +102 -0
- data/lib/astronoby/coordinates/horizontal.rb +13 -1
- data/lib/astronoby/distance.rb +41 -0
- data/lib/astronoby/duration.rb +116 -0
- data/lib/astronoby/earth_rotation.rb +70 -0
- data/lib/astronoby/equinox_solstice.rb +31 -8
- data/lib/astronoby/errors.rb +11 -0
- data/lib/astronoby/events/conjunction.rb +51 -0
- data/lib/astronoby/events/conjunction_opposition_calculator.rb +84 -0
- data/lib/astronoby/events/eclipse_phase.rb +27 -0
- data/lib/astronoby/events/extremum_calculator.rb +80 -0
- data/lib/astronoby/events/extremum_event.rb +15 -0
- data/lib/astronoby/events/greatest_elongation.rb +58 -0
- data/lib/astronoby/events/greatest_elongation_calculator.rb +56 -0
- data/lib/astronoby/events/lunar_eclipse.rb +99 -0
- data/lib/astronoby/events/lunar_eclipse_calculator.rb +285 -0
- data/lib/astronoby/events/opposition.rb +19 -0
- data/lib/astronoby/events/rise_transit_set_calculator.rb +9 -6
- data/lib/astronoby/events/rise_transit_set_event.rb +12 -1
- data/lib/astronoby/events/rise_transit_set_events.rb +12 -1
- data/lib/astronoby/events/twilight_calculator.rb +1 -1
- data/lib/astronoby/events/twilight_event.rb +24 -6
- data/lib/astronoby/events/twilight_events.rb +26 -6
- data/lib/astronoby/extremum_finder.rb +148 -0
- data/lib/astronoby/instant.rb +35 -9
- data/lib/astronoby/libration.rb +25 -0
- data/lib/astronoby/mean_obliquity.rb +8 -0
- data/lib/astronoby/moon_orientation_ephemeris.rb +69 -0
- data/lib/astronoby/moon_physical_ephemeris.rb +263 -0
- data/lib/astronoby/nutation.rb +10 -20
- data/lib/astronoby/observer.rb +67 -49
- data/lib/astronoby/orientation.rb +107 -0
- data/lib/astronoby/position.rb +16 -0
- data/lib/astronoby/precession.rb +61 -60
- data/lib/astronoby/reference_frame.rb +73 -7
- data/lib/astronoby/reference_frames/apparent.rb +25 -16
- data/lib/astronoby/reference_frames/astrometric.rb +14 -1
- data/lib/astronoby/reference_frames/geometric.rb +7 -1
- data/lib/astronoby/reference_frames/mean_of_date.rb +13 -1
- data/lib/astronoby/reference_frames/teme.rb +153 -0
- data/lib/astronoby/reference_frames/topocentric.rb +31 -5
- data/lib/astronoby/refraction.rb +26 -5
- data/lib/astronoby/root_finder.rb +83 -0
- data/lib/astronoby/rotation.rb +49 -0
- data/lib/astronoby/stellar_propagation.rb +162 -0
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +31 -0
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +101 -0
- data/lib/astronoby/time/greenwich_sidereal_time.rb +41 -58
- data/lib/astronoby/time/local_apparent_sidereal_time.rb +63 -0
- data/lib/astronoby/time/local_mean_sidereal_time.rb +63 -0
- data/lib/astronoby/time/local_sidereal_time.rb +59 -26
- data/lib/astronoby/time/sidereal_time.rb +64 -0
- data/lib/astronoby/true_obliquity.rb +4 -0
- data/lib/astronoby/util/maths.rb +8 -0
- data/lib/astronoby/util/time.rb +10 -467
- data/lib/astronoby/vector.rb +10 -0
- data/lib/astronoby/velocity.rb +44 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +33 -0
- metadata +58 -6
data/lib/astronoby/distance.rb
CHANGED
|
@@ -1,87 +1,128 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents a distance with meters as its internal representation.
|
|
5
|
+
# Provides conversions between meters, kilometers, astronomical units,
|
|
6
|
+
# and parsecs.
|
|
7
|
+
#
|
|
8
|
+
# @example Create a distance from astronomical units
|
|
9
|
+
# distance = Astronoby::Distance.from_au(1.0)
|
|
10
|
+
# distance.km # => 149597870.7
|
|
11
|
+
#
|
|
4
12
|
class Distance
|
|
5
13
|
include Comparable
|
|
6
14
|
|
|
7
15
|
class << self
|
|
16
|
+
# @return [Astronoby::Distance] a zero distance
|
|
8
17
|
def zero
|
|
9
18
|
new(0)
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @param meters [Numeric] the distance in meters
|
|
22
|
+
# @return [Astronoby::Distance] a new Distance
|
|
12
23
|
def from_meters(meters)
|
|
13
24
|
new(meters)
|
|
14
25
|
end
|
|
15
26
|
alias_method :from_m, :from_meters
|
|
16
27
|
|
|
28
|
+
# @param kilometers [Numeric] the distance in kilometers
|
|
29
|
+
# @return [Astronoby::Distance] a new Distance
|
|
17
30
|
def from_kilometers(kilometers)
|
|
18
31
|
meters = kilometers * Constants::KILOMETER_IN_METERS
|
|
19
32
|
from_meters(meters)
|
|
20
33
|
end
|
|
21
34
|
alias_method :from_km, :from_kilometers
|
|
22
35
|
|
|
36
|
+
# @param astronomical_units [Numeric] the distance in AU
|
|
37
|
+
# @return [Astronoby::Distance] a new Distance
|
|
23
38
|
def from_astronomical_units(astronomical_units)
|
|
24
39
|
meters = astronomical_units * Constants::ASTRONOMICAL_UNIT_IN_METERS
|
|
25
40
|
from_meters(meters)
|
|
26
41
|
end
|
|
27
42
|
alias_method :from_au, :from_astronomical_units
|
|
28
43
|
|
|
44
|
+
# @param parsecs [Numeric] the distance in parsecs
|
|
45
|
+
# @return [Astronoby::Distance] a new Distance
|
|
46
|
+
def from_parsecs(parsecs)
|
|
47
|
+
meters = parsecs * Constants::PARSEC_IN_METERS
|
|
48
|
+
from_meters(meters)
|
|
49
|
+
end
|
|
50
|
+
alias_method :from_pc, :from_parsecs
|
|
51
|
+
|
|
52
|
+
# @param array [Array<Numeric>] array of meter values
|
|
53
|
+
# @return [Astronoby::Vector<Astronoby::Distance>] a vector of Distances
|
|
29
54
|
def vector_from_meters(array)
|
|
30
55
|
Vector.elements(array.map { from_meters(_1) })
|
|
31
56
|
end
|
|
32
57
|
alias_method :vector_from_m, :vector_from_meters
|
|
33
58
|
end
|
|
34
59
|
|
|
60
|
+
# @return [Numeric] the distance in meters
|
|
35
61
|
attr_reader :meters
|
|
36
62
|
alias_method :m, :meters
|
|
37
63
|
|
|
64
|
+
# @param meters [Numeric] the distance in meters
|
|
38
65
|
def initialize(meters)
|
|
39
66
|
@meters = meters
|
|
40
67
|
freeze
|
|
41
68
|
end
|
|
42
69
|
|
|
70
|
+
# @return [Float] the distance in kilometers
|
|
43
71
|
def kilometers
|
|
44
72
|
@meters / Constants::KILOMETER_IN_METERS.to_f
|
|
45
73
|
end
|
|
46
74
|
alias_method :km, :kilometers
|
|
47
75
|
|
|
76
|
+
# @return [Float] the distance in astronomical units
|
|
48
77
|
def astronomical_units
|
|
49
78
|
@meters / Constants::ASTRONOMICAL_UNIT_IN_METERS.to_f
|
|
50
79
|
end
|
|
51
80
|
alias_method :au, :astronomical_units
|
|
52
81
|
|
|
82
|
+
# @param other [Astronoby::Distance] distance to add
|
|
83
|
+
# @return [Astronoby::Distance] the sum
|
|
53
84
|
def +(other)
|
|
54
85
|
self.class.from_meters(meters + other.meters)
|
|
55
86
|
end
|
|
56
87
|
|
|
88
|
+
# @param other [Astronoby::Distance] distance to subtract
|
|
89
|
+
# @return [Astronoby::Distance] the difference
|
|
57
90
|
def -(other)
|
|
58
91
|
self.class.from_meters(@meters - other.meters)
|
|
59
92
|
end
|
|
60
93
|
|
|
94
|
+
# @return [Astronoby::Distance] the negated distance
|
|
61
95
|
def -@
|
|
62
96
|
self.class.from_meters(-@meters)
|
|
63
97
|
end
|
|
64
98
|
|
|
99
|
+
# @return [Boolean] true if the distance is positive
|
|
65
100
|
def positive?
|
|
66
101
|
meters > 0
|
|
67
102
|
end
|
|
68
103
|
|
|
104
|
+
# @return [Boolean] true if the distance is negative
|
|
69
105
|
def negative?
|
|
70
106
|
meters < 0
|
|
71
107
|
end
|
|
72
108
|
|
|
109
|
+
# @return [Boolean] true if the distance is zero
|
|
73
110
|
def zero?
|
|
74
111
|
meters.zero?
|
|
75
112
|
end
|
|
76
113
|
|
|
114
|
+
# @return [Numeric] the square of the distance in meters
|
|
77
115
|
def abs2
|
|
78
116
|
meters**2
|
|
79
117
|
end
|
|
80
118
|
|
|
119
|
+
# @return [Integer] hash value
|
|
81
120
|
def hash
|
|
82
121
|
[meters, self.class].hash
|
|
83
122
|
end
|
|
84
123
|
|
|
124
|
+
# @param other [Astronoby::Distance] distance to compare with
|
|
125
|
+
# @return [Integer, nil] -1, 0, or 1; nil if not comparable
|
|
85
126
|
def <=>(other)
|
|
86
127
|
return unless other.is_a?(self.class)
|
|
87
128
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class Duration
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# @return [Astronoby::Duration] a zero duration
|
|
9
|
+
def zero
|
|
10
|
+
new(0)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param seconds [Numeric] the duration in seconds
|
|
14
|
+
# @return [Astronoby::Duration] a new Duration
|
|
15
|
+
def from_seconds(seconds)
|
|
16
|
+
new(seconds)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param minutes [Numeric] the duration in minutes
|
|
20
|
+
# @return [Astronoby::Duration] a new Duration
|
|
21
|
+
def from_minutes(minutes)
|
|
22
|
+
seconds = minutes * Constants::SECONDS_PER_MINUTE
|
|
23
|
+
from_seconds(seconds)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param hours [Numeric] the duration in hours
|
|
27
|
+
# @return [Astronoby::Duration] a new Duration
|
|
28
|
+
def from_hours(hours)
|
|
29
|
+
seconds = hours * Constants::SECONDS_PER_HOUR
|
|
30
|
+
from_seconds(seconds)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param days [Numeric] the duration in days
|
|
34
|
+
# @return [Astronoby::Duration] a new Duration
|
|
35
|
+
def from_days(days)
|
|
36
|
+
seconds = days * Constants::SECONDS_PER_DAY
|
|
37
|
+
from_seconds(seconds)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Numeric] the duration in seconds
|
|
42
|
+
attr_reader :seconds
|
|
43
|
+
|
|
44
|
+
# @param seconds [Numeric] the duration in seconds
|
|
45
|
+
def initialize(seconds)
|
|
46
|
+
@seconds = seconds
|
|
47
|
+
freeze
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Float] the duration in minutes
|
|
51
|
+
def minutes
|
|
52
|
+
@seconds / Constants::SECONDS_PER_MINUTE
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [Float] the duration in hours
|
|
56
|
+
def hours
|
|
57
|
+
@seconds / Constants::SECONDS_PER_HOUR
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Float] the duration in days
|
|
61
|
+
def days
|
|
62
|
+
@seconds / Constants::SECONDS_PER_DAY
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param other [Astronoby::Duration] duration to add
|
|
66
|
+
# @return [Astronoby::Duration] the sum
|
|
67
|
+
def +(other)
|
|
68
|
+
self.class.from_seconds(@seconds + other.seconds)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param other [Astronoby::Duration] duration to subtract
|
|
72
|
+
# @return [Astronoby::Duration] the difference
|
|
73
|
+
def -(other)
|
|
74
|
+
self.class.from_seconds(@seconds - other.seconds)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Astronoby::Duration] the negated duration
|
|
78
|
+
def -@
|
|
79
|
+
self.class.from_seconds(-@seconds)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Astronoby::Duration] the absolute duration
|
|
83
|
+
def abs
|
|
84
|
+
self.class.from_seconds(@seconds.abs)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Boolean] true if the duration is positive
|
|
88
|
+
def positive?
|
|
89
|
+
@seconds > 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean] true if the duration is negative
|
|
93
|
+
def negative?
|
|
94
|
+
@seconds < 0
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [Boolean] true if the duration is zero
|
|
98
|
+
def zero?
|
|
99
|
+
@seconds.zero?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Integer] hash value
|
|
103
|
+
def hash
|
|
104
|
+
[@seconds, self.class].hash
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @param other [Astronoby::Duration] duration to compare with
|
|
108
|
+
# @return [Integer, nil] -1, 0, or 1; nil if not comparable
|
|
109
|
+
def <=>(other)
|
|
110
|
+
return unless other.is_a?(self.class)
|
|
111
|
+
|
|
112
|
+
seconds <=> other.seconds
|
|
113
|
+
end
|
|
114
|
+
alias_method :eql?, :==
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# Computes Earth rotation matrices using Greenwich Sidereal Time.
|
|
5
|
+
# Provides both apparent (GAST-based) and mean (GMST-based) rotation
|
|
6
|
+
# matrices for transforming between Earth-fixed and celestial frames.
|
|
7
|
+
class EarthRotation
|
|
8
|
+
# Computes the apparent Earth rotation matrix R₃(GAST) for a given
|
|
9
|
+
# instant.
|
|
10
|
+
#
|
|
11
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
12
|
+
# @return [Matrix] 3x3 Earth rotation matrix
|
|
13
|
+
def self.matrix_for(instant)
|
|
14
|
+
new(instant: instant).matrix
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Computes the mean Earth rotation matrix R₃(GMST) for a given
|
|
18
|
+
# instant.
|
|
19
|
+
#
|
|
20
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
21
|
+
# @return [Matrix] 3x3 mean Earth rotation matrix
|
|
22
|
+
def self.mean_matrix_for(instant)
|
|
23
|
+
new(instant: instant).mean_matrix
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param instant [Astronoby::Instant] the time instant
|
|
27
|
+
def initialize(instant:)
|
|
28
|
+
@instant = instant
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Computes R₃(GAST), the apparent Earth rotation matrix.
|
|
32
|
+
# GAST = GMST + equation of the equinoxes (Δψ cos ε₀).
|
|
33
|
+
#
|
|
34
|
+
# @return [Matrix] 3x3 rotation matrix
|
|
35
|
+
def matrix
|
|
36
|
+
rotation_matrix_for(gast)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Computes R₃(GMST), the mean Earth rotation matrix.
|
|
40
|
+
#
|
|
41
|
+
# @return [Matrix] 3x3 rotation matrix
|
|
42
|
+
def mean_matrix
|
|
43
|
+
rotation_matrix_for(gmst)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def gmst
|
|
49
|
+
Angle.from_hours(@instant.gmst)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def gast
|
|
53
|
+
nutation = Nutation.new(instant: @instant)
|
|
54
|
+
dpsi = nutation.nutation_in_longitude
|
|
55
|
+
mean_obliquity = MeanObliquity.at(@instant)
|
|
56
|
+
Angle.from_radians(
|
|
57
|
+
gmst.radians + dpsi.radians * mean_obliquity.cos
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rotation_matrix_for(angle)
|
|
62
|
+
c, s = angle.cos, angle.sin
|
|
63
|
+
Matrix[
|
|
64
|
+
[c, -s, 0],
|
|
65
|
+
[s, c, 0],
|
|
66
|
+
[0, 0, 1]
|
|
67
|
+
]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Computes the time of equinoxes and solstices for a given year.
|
|
5
|
+
#
|
|
6
|
+
# Source:
|
|
7
|
+
# Title: Astronomical Algorithms
|
|
8
|
+
# Author: Jean Meeus
|
|
9
|
+
# Edition: 2nd edition
|
|
10
|
+
# Chapter: 27 - Equinoxes and Soltices
|
|
4
11
|
class EquinoxSolstice
|
|
5
|
-
# Source:
|
|
6
|
-
# Title: Astronomical Algorithms
|
|
7
|
-
# Author: Jean Meeus
|
|
8
|
-
# Edition: 2nd edition
|
|
9
|
-
# Chapter: 27 - Equinoxes and Soltices
|
|
10
|
-
|
|
11
12
|
EVENTS = [
|
|
12
13
|
MARCH_EQUINOX = 0,
|
|
13
14
|
JUNE_SOLSTICE = 1,
|
|
@@ -73,22 +74,39 @@ module Astronoby
|
|
|
73
74
|
[8, 15.45, 16859.074]
|
|
74
75
|
].freeze
|
|
75
76
|
|
|
77
|
+
# @param year [Integer] the year
|
|
78
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
79
|
+
# @return [Time] time of the March equinox
|
|
76
80
|
def self.march_equinox(year, ephem)
|
|
77
81
|
new(year, MARCH_EQUINOX, ephem).time
|
|
78
82
|
end
|
|
79
83
|
|
|
84
|
+
# @param year [Integer] the year
|
|
85
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
86
|
+
# @return [Time] time of the June solstice
|
|
80
87
|
def self.june_solstice(year, ephem)
|
|
81
88
|
new(year, JUNE_SOLSTICE, ephem).time
|
|
82
89
|
end
|
|
83
90
|
|
|
91
|
+
# @param year [Integer] the year
|
|
92
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
93
|
+
# @return [Time] time of the September equinox
|
|
84
94
|
def self.september_equinox(year, ephem)
|
|
85
95
|
new(year, SEPTEMBER_EQUINOX, ephem).time
|
|
86
96
|
end
|
|
87
97
|
|
|
98
|
+
# @param year [Integer] the year
|
|
99
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
100
|
+
# @return [Time] time of the December solstice
|
|
88
101
|
def self.december_solstice(year, ephem)
|
|
89
102
|
new(year, DECEMBER_SOLSTICE, ephem).time
|
|
90
103
|
end
|
|
91
104
|
|
|
105
|
+
# @param year [Integer] the year
|
|
106
|
+
# @param event [Integer] one of MARCH_EQUINOX, JUNE_SOLSTICE,
|
|
107
|
+
# SEPTEMBER_EQUINOX, or DECEMBER_SOLSTICE
|
|
108
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
109
|
+
# @raise [Astronoby::UnsupportedEventError] if event is invalid
|
|
92
110
|
def initialize(year, event, ephem)
|
|
93
111
|
unless EVENTS.include?(event)
|
|
94
112
|
raise UnsupportedEventError.new(
|
|
@@ -97,12 +115,13 @@ module Astronoby
|
|
|
97
115
|
end
|
|
98
116
|
|
|
99
117
|
@event = event
|
|
118
|
+
@ephem = ephem
|
|
100
119
|
@year = (year.to_i - 2000) / 1000.0
|
|
101
120
|
uncorrected_time = compute
|
|
102
121
|
@instant = Instant.from_time(uncorrected_time)
|
|
103
|
-
@sun = Sun.new(ephem: ephem, instant: @instant)
|
|
104
122
|
end
|
|
105
123
|
|
|
124
|
+
# @return [Time] the corrected UTC time of the event
|
|
106
125
|
def time
|
|
107
126
|
Instant.from_terrestrial_time(@instant.tt + corrected).to_time.round
|
|
108
127
|
end
|
|
@@ -135,8 +154,12 @@ module Astronoby
|
|
|
135
154
|
component[4] * @year**4
|
|
136
155
|
end
|
|
137
156
|
|
|
157
|
+
def sun
|
|
158
|
+
@sun ||= Sun.new(ephem: @ephem, instant: @instant)
|
|
159
|
+
end
|
|
160
|
+
|
|
138
161
|
def corrected
|
|
139
|
-
longitude =
|
|
162
|
+
longitude = sun.apparent.ecliptic.longitude
|
|
140
163
|
58 * Angle.from_degrees(@event * 90 - longitude.degrees).sin
|
|
141
164
|
end
|
|
142
165
|
end
|
data/lib/astronoby/errors.rb
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
module Astronoby
|
|
2
|
+
# Raised when arguments are mutually incompatible.
|
|
2
3
|
class IncompatibleArgumentsError < ArgumentError; end
|
|
3
4
|
|
|
5
|
+
# Raised when an unsupported format is requested.
|
|
4
6
|
class UnsupportedFormatError < ArgumentError; end
|
|
5
7
|
|
|
8
|
+
# Raised when an unsupported event type is requested.
|
|
6
9
|
class UnsupportedEventError < ArgumentError; end
|
|
7
10
|
|
|
11
|
+
# Raised when an astronomical calculation fails.
|
|
8
12
|
class CalculationError < StandardError; end
|
|
9
13
|
|
|
14
|
+
# Raised when there is an error with ephemeris data.
|
|
10
15
|
class EphemerisError < StandardError; end
|
|
16
|
+
|
|
17
|
+
# Raised when an orientation kernel is not a supported orientation source.
|
|
18
|
+
class OrientationError < StandardError; end
|
|
19
|
+
|
|
20
|
+
# Raised when an orientation kernel does not cover the requested instant.
|
|
21
|
+
class OrientationOutOfRangeError < StandardError; end
|
|
11
22
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class Conjunction
|
|
5
|
+
INFERIOR = :inferior
|
|
6
|
+
SUPERIOR = :superior
|
|
7
|
+
|
|
8
|
+
# @param instant [Astronoby::Instant] when the conjunction occurs
|
|
9
|
+
# @param body [Astronoby::Body] the body in conjunction with the Sun
|
|
10
|
+
# @return [Astronoby::Conjunction] an inferior conjunction
|
|
11
|
+
def self.inferior(instant:, body:)
|
|
12
|
+
new(instant: instant, body: body, subtype: INFERIOR)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param instant [Astronoby::Instant] when the conjunction occurs
|
|
16
|
+
# @param body [Astronoby::Body] the body in conjunction with the Sun
|
|
17
|
+
# @return [Astronoby::Conjunction] a superior conjunction
|
|
18
|
+
def self.superior(instant:, body:)
|
|
19
|
+
new(instant: instant, body: body, subtype: SUPERIOR)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Astronoby::Instant] when the conjunction occurs
|
|
23
|
+
attr_reader :instant
|
|
24
|
+
|
|
25
|
+
# @return [Astronoby::Body] the body in conjunction with the Sun
|
|
26
|
+
attr_reader :body
|
|
27
|
+
|
|
28
|
+
# @return [Symbol] +INFERIOR+ or +SUPERIOR+
|
|
29
|
+
attr_reader :subtype
|
|
30
|
+
|
|
31
|
+
# @param instant [Astronoby::Instant] when the conjunction occurs
|
|
32
|
+
# @param body [Astronoby::Body] the body in conjunction with the Sun
|
|
33
|
+
# @param subtype [Symbol] +INFERIOR+ or +SUPERIOR+
|
|
34
|
+
def initialize(instant:, body:, subtype:)
|
|
35
|
+
@instant = instant
|
|
36
|
+
@body = body
|
|
37
|
+
@subtype = subtype
|
|
38
|
+
freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Boolean] true for an inferior conjunction
|
|
42
|
+
def inferior?
|
|
43
|
+
@subtype == INFERIOR
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Boolean] true for a superior conjunction
|
|
47
|
+
def superior?
|
|
48
|
+
@subtype == SUPERIOR
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class ConjunctionOppositionCalculator
|
|
5
|
+
# @param body [Astronoby::SolarSystemBody] the planet to track
|
|
6
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
7
|
+
# @param samples_per_period [Integer] number of samples per synodic period
|
|
8
|
+
def initialize(body:, ephem:, samples_per_period: 60)
|
|
9
|
+
@body = body
|
|
10
|
+
@ephem = ephem
|
|
11
|
+
@samples_per_period = samples_per_period
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param start_time [Time] start time
|
|
15
|
+
# @param end_time [Time] end time
|
|
16
|
+
# @return [Array<Astronoby::Conjunction>] conjunctions in the range
|
|
17
|
+
def conjunction_events_between(start_time, end_time)
|
|
18
|
+
roots_between(start_time, end_time, accept: method(:conjunction?))
|
|
19
|
+
.map { |jd| conjunction_at(Instant.from_terrestrial_time(jd)) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param start_time [Time] start time
|
|
23
|
+
# @param end_time [Time] end time
|
|
24
|
+
# @return [Array<Astronoby::Opposition>] oppositions in the range
|
|
25
|
+
def opposition_events_between(start_time, end_time)
|
|
26
|
+
roots_between(start_time, end_time, accept: method(:opposition?))
|
|
27
|
+
.map do |jd|
|
|
28
|
+
Opposition.new(
|
|
29
|
+
instant: Instant.from_terrestrial_time(jd),
|
|
30
|
+
body: @body
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def roots_between(start_time, end_time, accept:)
|
|
38
|
+
finder.roots(
|
|
39
|
+
Instant.from_time(start_time).tt,
|
|
40
|
+
Instant.from_time(end_time).tt,
|
|
41
|
+
accept: accept
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def finder
|
|
46
|
+
@finder ||= RootFinder.new(
|
|
47
|
+
value_at: ->(jd) { delta_longitude_at(jd).sin },
|
|
48
|
+
period: synodic_period,
|
|
49
|
+
samples_per_period: @samples_per_period
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def conjunction?(jd)
|
|
54
|
+
delta_longitude_at(jd).cos.positive?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def opposition?(jd)
|
|
58
|
+
delta_longitude_at(jd).cos.negative?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def delta_longitude_at(jd)
|
|
62
|
+
instant = Instant.from_terrestrial_time(jd)
|
|
63
|
+
planet = @body.new(instant: instant, ephem: @ephem)
|
|
64
|
+
sun = Sun.new(instant: instant, ephem: @ephem)
|
|
65
|
+
planet.apparent.ecliptic.longitude - sun.apparent.ecliptic.longitude
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def conjunction_at(instant)
|
|
69
|
+
planet = @body.new(instant: instant, ephem: @ephem)
|
|
70
|
+
sun = Sun.new(instant: instant, ephem: @ephem)
|
|
71
|
+
|
|
72
|
+
if planet.apparent.distance < sun.apparent.distance
|
|
73
|
+
Conjunction.inferior(instant: instant, body: @body)
|
|
74
|
+
else
|
|
75
|
+
Conjunction.superior(instant: instant, body: @body)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def synodic_period
|
|
80
|
+
@synodic_period ||=
|
|
81
|
+
1.0 / ((1.0 / @body::ORBITAL_PERIOD) - (1.0 / Earth::ORBITAL_PERIOD)).abs
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# A bounded phase of an eclipse, delimited by its two boundary instants.
|
|
5
|
+
class EclipsePhase
|
|
6
|
+
# @return [Astronoby::Instant] when the phase begins
|
|
7
|
+
attr_reader :starting_instant
|
|
8
|
+
|
|
9
|
+
# @return [Astronoby::Instant] when the phase ends
|
|
10
|
+
attr_reader :ending_instant
|
|
11
|
+
|
|
12
|
+
# @param starting_instant [Astronoby::Instant] when the phase begins
|
|
13
|
+
# @param ending_instant [Astronoby::Instant] when the phase ends
|
|
14
|
+
def initialize(starting_instant:, ending_instant:)
|
|
15
|
+
@starting_instant = starting_instant
|
|
16
|
+
@ending_instant = ending_instant
|
|
17
|
+
freeze
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Astronoby::Duration] phase duration
|
|
21
|
+
def duration
|
|
22
|
+
Duration.from_seconds(
|
|
23
|
+
(@ending_instant.to_time - @starting_instant.to_time).round
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class ExtremumCalculator
|
|
5
|
+
# @param body [Astronoby::SolarSystemBody] the celestial body to track
|
|
6
|
+
# @param primary_body [Astronoby::SolarSystemBody] the reference body
|
|
7
|
+
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
8
|
+
# @param samples_per_period [Integer] number of samples per orbital period
|
|
9
|
+
def initialize(
|
|
10
|
+
body:,
|
|
11
|
+
primary_body:,
|
|
12
|
+
ephem:,
|
|
13
|
+
samples_per_period: 60
|
|
14
|
+
)
|
|
15
|
+
@body = body
|
|
16
|
+
@primary_body = primary_body
|
|
17
|
+
@ephem = ephem
|
|
18
|
+
@samples_per_period = samples_per_period
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Finds all apoapsis events between two times
|
|
22
|
+
# @param start_time [Time] Start time
|
|
23
|
+
# @param end_time [Time] End time
|
|
24
|
+
# @return [Array<Astronoby::ExtremumEvent>] Array of apoapsis events
|
|
25
|
+
def apoapsis_events_between(start_time, end_time)
|
|
26
|
+
find_extrema(
|
|
27
|
+
Instant.from_time(start_time).tt,
|
|
28
|
+
Instant.from_time(end_time).tt,
|
|
29
|
+
type: :maximum
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Finds all periapsis events between two times
|
|
34
|
+
# @param start_time [Time] Start time
|
|
35
|
+
# @param end_time [Time] End time
|
|
36
|
+
# @return [Array<Astronoby::ExtremumEvent>] Array of periapsis events
|
|
37
|
+
def periapsis_events_between(start_time, end_time)
|
|
38
|
+
find_extrema(
|
|
39
|
+
Instant.from_time(start_time).tt,
|
|
40
|
+
Instant.from_time(end_time).tt,
|
|
41
|
+
type: :minimum
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Finds extrema (minima or maxima) in the distance between bodies within
|
|
46
|
+
# a time range
|
|
47
|
+
# @param start_jd [Float] Start time in Julian Date (Terrestrial Time)
|
|
48
|
+
# @param end_jd [Float] End time in Julian Date (Terrestrial Time)
|
|
49
|
+
# @param type [Symbol] :maximum or :minimum
|
|
50
|
+
# @return [Array<Astronoby::ExtremumEvent>] Array of extrema events
|
|
51
|
+
def find_extrema(start_jd, end_jd, type: :maximum)
|
|
52
|
+
finder.extrema(start_jd, end_jd, type: type).map do |extremum|
|
|
53
|
+
ExtremumEvent.new(
|
|
54
|
+
Instant.from_terrestrial_time(extremum[:jd]),
|
|
55
|
+
extremum[:value]
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def finder
|
|
63
|
+
@finder ||= ExtremumFinder.new(
|
|
64
|
+
value_at: method(:distance_at),
|
|
65
|
+
period: @body::ORBITAL_PERIOD,
|
|
66
|
+
samples_per_period: @samples_per_period
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def distance_at(jd)
|
|
71
|
+
instant = Instant.from_terrestrial_time(jd)
|
|
72
|
+
body_geometric = @body.geometric(ephem: @ephem, instant: instant)
|
|
73
|
+
primary_geometric = @primary_body
|
|
74
|
+
.geometric(ephem: @ephem, instant: instant)
|
|
75
|
+
|
|
76
|
+
distance_vector = body_geometric.position - primary_geometric.position
|
|
77
|
+
distance_vector.magnitude
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# Represents an extremum event with its timing and value
|
|
5
|
+
class ExtremumEvent
|
|
6
|
+
attr_reader :instant, :value
|
|
7
|
+
|
|
8
|
+
# @param instant [Astronoby::Instant] When the event occurs
|
|
9
|
+
# @param value [Object] The extreme value
|
|
10
|
+
def initialize(instant, value)
|
|
11
|
+
@instant = instant
|
|
12
|
+
@value = value
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|