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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +235 -0
  4. data/README.md +19 -1
  5. data/UPGRADING.md +270 -0
  6. data/docs/README.md +105 -16
  7. data/docs/altitude_crossings.md +92 -0
  8. data/docs/angles.md +1 -0
  9. data/docs/configuration.md +28 -17
  10. data/docs/coordinates.md +72 -12
  11. data/docs/deep_sky_bodies.md +46 -1
  12. data/docs/ephem.md +5 -2
  13. data/docs/equinoxes_solstices_times.md +4 -3
  14. data/docs/glossary.md +99 -1
  15. data/docs/iers.md +40 -0
  16. data/docs/instant.md +33 -21
  17. data/docs/lunar_eclipses.md +208 -0
  18. data/docs/lunar_observation.md +87 -0
  19. data/docs/moon_phases.md +4 -1
  20. data/docs/observer.md +20 -6
  21. data/docs/planetary_phenomena.md +78 -0
  22. data/docs/reference_frames.md +192 -34
  23. data/docs/rise_transit_set_times.md +6 -4
  24. data/docs/solar_system_bodies.md +26 -4
  25. data/docs/twilight_times.md +43 -24
  26. data/lib/astronoby/angle.rb +63 -2
  27. data/lib/astronoby/angles/dms.rb +18 -1
  28. data/lib/astronoby/angles/hms.rb +14 -1
  29. data/lib/astronoby/angular_velocity.rb +21 -0
  30. data/lib/astronoby/bodies/deep_sky_object.rb +6 -1
  31. data/lib/astronoby/bodies/deep_sky_object_position.rb +35 -38
  32. data/lib/astronoby/bodies/earth.rb +7 -44
  33. data/lib/astronoby/bodies/jupiter.rb +10 -0
  34. data/lib/astronoby/bodies/mars.rb +10 -0
  35. data/lib/astronoby/bodies/mercury.rb +10 -0
  36. data/lib/astronoby/bodies/moon.rb +158 -32
  37. data/lib/astronoby/bodies/neptune.rb +10 -0
  38. data/lib/astronoby/bodies/saturn.rb +10 -0
  39. data/lib/astronoby/bodies/solar_system_body.rb +240 -61
  40. data/lib/astronoby/bodies/sun.rb +79 -4
  41. data/lib/astronoby/bodies/uranus.rb +10 -0
  42. data/lib/astronoby/bodies/venus.rb +10 -0
  43. data/lib/astronoby/body.rb +6 -0
  44. data/lib/astronoby/cache.rb +13 -0
  45. data/lib/astronoby/center.rb +84 -0
  46. data/lib/astronoby/constants.rb +1 -2
  47. data/lib/astronoby/constellation.rb +9 -1
  48. data/lib/astronoby/constellations/data.rb +1 -1
  49. data/lib/astronoby/coordinates/ecliptic.rb +10 -1
  50. data/lib/astronoby/coordinates/equatorial.rb +79 -13
  51. data/lib/astronoby/coordinates/geodetic.rb +102 -0
  52. data/lib/astronoby/coordinates/horizontal.rb +13 -1
  53. data/lib/astronoby/delta_t.rb +44 -0
  54. data/lib/astronoby/distance.rb +35 -0
  55. data/lib/astronoby/duration.rb +116 -0
  56. data/lib/astronoby/earth_rotation.rb +70 -0
  57. data/lib/astronoby/equinox_solstice.rb +31 -8
  58. data/lib/astronoby/errors.rb +11 -0
  59. data/lib/astronoby/events/altitude_crossing.rb +29 -0
  60. data/lib/astronoby/events/altitude_crossing_calculator.rb +511 -0
  61. data/lib/astronoby/events/altitude_extreme.rb +31 -0
  62. data/lib/astronoby/events/conjunction.rb +51 -0
  63. data/lib/astronoby/events/conjunction_opposition_calculator.rb +84 -0
  64. data/lib/astronoby/events/extremum_calculator.rb +23 -176
  65. data/lib/astronoby/events/greatest_elongation.rb +58 -0
  66. data/lib/astronoby/events/greatest_elongation_calculator.rb +56 -0
  67. data/lib/astronoby/events/lunar_eclipse.rb +111 -0
  68. data/lib/astronoby/events/lunar_eclipse_calculator.rb +397 -0
  69. data/lib/astronoby/events/lunar_eclipse_geometry.rb +115 -0
  70. data/lib/astronoby/events/lunar_eclipse_phase.rb +45 -0
  71. data/lib/astronoby/events/lunar_eclipse_visibility.rb +296 -0
  72. data/lib/astronoby/events/opposition.rb +19 -0
  73. data/lib/astronoby/events/rise_transit_set_calculator.rb +9 -13
  74. data/lib/astronoby/events/rise_transit_set_event.rb +12 -1
  75. data/lib/astronoby/events/rise_transit_set_events.rb +12 -1
  76. data/lib/astronoby/events/twilight_calculator.rb +37 -147
  77. data/lib/astronoby/events/twilight_event.rb +24 -6
  78. data/lib/astronoby/events/twilight_events.rb +26 -6
  79. data/lib/astronoby/extremum_finder.rb +148 -0
  80. data/lib/astronoby/horizon.rb +40 -0
  81. data/lib/astronoby/horizontal_transformation.rb +112 -0
  82. data/lib/astronoby/instant.rb +56 -16
  83. data/lib/astronoby/libration.rb +25 -0
  84. data/lib/astronoby/mean_obliquity.rb +8 -0
  85. data/lib/astronoby/moon_orientation_ephemeris.rb +69 -0
  86. data/lib/astronoby/moon_physical_ephemeris.rb +263 -0
  87. data/lib/astronoby/nutation.rb +10 -20
  88. data/lib/astronoby/observer.rb +67 -49
  89. data/lib/astronoby/orientation.rb +107 -0
  90. data/lib/astronoby/position.rb +16 -0
  91. data/lib/astronoby/precession.rb +62 -61
  92. data/lib/astronoby/reference_frame.rb +80 -9
  93. data/lib/astronoby/reference_frames/apparent.rb +43 -12
  94. data/lib/astronoby/reference_frames/astrometric.rb +14 -1
  95. data/lib/astronoby/reference_frames/geometric.rb +7 -1
  96. data/lib/astronoby/reference_frames/mean_of_date.rb +19 -1
  97. data/lib/astronoby/reference_frames/teme.rb +159 -0
  98. data/lib/astronoby/reference_frames/topocentric.rb +36 -4
  99. data/lib/astronoby/refraction.rb +26 -5
  100. data/lib/astronoby/root_finder.rb +90 -0
  101. data/lib/astronoby/rotation.rb +49 -0
  102. data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +19 -8
  103. data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +44 -5
  104. data/lib/astronoby/time/greenwich_sidereal_time.rb +21 -0
  105. data/lib/astronoby/time/local_apparent_sidereal_time.rb +21 -0
  106. data/lib/astronoby/time/local_mean_sidereal_time.rb +21 -0
  107. data/lib/astronoby/time/local_sidereal_time.rb +24 -0
  108. data/lib/astronoby/time/sidereal_time.rb +23 -1
  109. data/lib/astronoby/true_obliquity.rb +4 -0
  110. data/lib/astronoby/util/maths.rb +8 -0
  111. data/lib/astronoby/util/time.rb +0 -497
  112. data/lib/astronoby/vector.rb +10 -0
  113. data/lib/astronoby/velocity.rb +39 -0
  114. data/lib/astronoby/version.rb +1 -1
  115. data/lib/astronoby.rb +30 -0
  116. metadata +80 -5
@@ -5,14 +5,15 @@ some of its light still illuminates the atmosphere, making the sky brighter than
5
5
  during full night.
6
6
 
7
7
  We usually define 4 moments when talking about twilight:
8
- * sunrise/sunset: right when the Sun goes above the horizon or right after it
8
+
9
+ - sunrise/sunset: right when the Sun goes above the horizon or right after it
9
10
  goes below the horizon. The Sun's horizon angle is 0°.
10
- * civil twilight: when the horizon angle is between 0° and -6°. Usually, during
11
+ - civil twilight: when the horizon angle is between 0° and -6°. Usually, during
11
12
  this time, artificial light is not needed yet.
12
- * nautical twilight: when the horizon angle is between -6° and -12°. When the
13
+ - nautical twilight: when the horizon angle is between -6° and -12°. When the
13
14
  nautical twilight starts, the difference between the horizon at sea and the
14
15
  sky cannot be seen clearly anymore.
15
- * astronomical twilight: when the horizon angle is between -12° and -18°. Some
16
+ - astronomical twilight: when the horizon angle is between -12° and -18°. Some
16
17
  stars can be seen during this time.
17
18
 
18
19
  These moments change every day and depend on the observer's location. They can
@@ -24,8 +25,9 @@ Once instantiated, the calculator doesn't do anything yet, it waits for your
24
25
  instruction.
25
26
 
26
27
  It takes as key arguments:
27
- * `observer` (`Astronoby::Observer`): location on Earth of the observer
28
- * `ephem`: ephemeris to provide the initial raw data
28
+
29
+ - `observer` (`Astronoby::Observer`): location on Earth of the observer
30
+ - `ephem`: ephemeris to provide the initial raw data
29
31
 
30
32
  You can learn more about ephemerides on the [Ephem page].
31
33
 
@@ -53,17 +55,18 @@ that will happen between two dates.
53
55
 
54
56
  It returns a `Astronoby::TwilightEvents` object which exposes the 6 following
55
57
  instance methods:
56
- * `#morning_astronomical_twilight_times`: when the rising Sun reaches 18° below
58
+
59
+ - `#morning_astronomical_twilight_times`: when the rising Sun reaches 18° below
57
60
  the horizon
58
- * `#morning_nautical_twilight_times`: when the rising Sun reaches 12° below the
61
+ - `#morning_nautical_twilight_times`: when the rising Sun reaches 12° below the
59
62
  horizon
60
- * `#morning_civil_twilight_times`: when the rising Sun reaches 6° below the
63
+ - `#morning_civil_twilight_times`: when the rising Sun reaches 6° below the
61
64
  horizon
62
- * `#evening_civil_twilight_times`: when the setting Sun reaches 6° below the
65
+ - `#evening_civil_twilight_times`: when the setting Sun reaches 6° below the
63
66
  horizon
64
- * `#evening_nautical_twilight_times`: when the setting Sun reaches 12° below the
67
+ - `#evening_nautical_twilight_times`: when the setting Sun reaches 12° below the
65
68
  horizon
66
- * `#evening_astronomical_twilight_times`: when the setting Sun reaches 18° below
69
+ - `#evening_astronomical_twilight_times`: when the setting Sun reaches 18° below
67
70
  the horizon
68
71
 
69
72
  ```rb
@@ -74,13 +77,13 @@ events = calculator.events_between(
74
77
 
75
78
  events.morning_civil_twilight_times
76
79
  # =>
77
- # [2025-08-01 02:29:17 UTC,
78
- # 2025-08-02 02:30:21 UTC,
79
- # 2025-08-03 02:31:26 UTC,
80
- # 2025-08-04 02:32:30 UTC,
81
- # 2025-08-05 02:33:35 UTC,
82
- # 2025-08-06 02:34:40 UTC,
83
- # 2025-08-07 02:35:45 UTC]
80
+ # [2025-08-01 02:29:11 UTC,
81
+ # 2025-08-02 02:30:15 UTC,
82
+ # 2025-08-03 02:31:20 UTC,
83
+ # 2025-08-04 02:32:24 UTC,
84
+ # 2025-08-05 02:33:29 UTC,
85
+ # 2025-08-06 02:34:34 UTC,
86
+ # 2025-08-07 02:35:39 UTC]
84
87
  ```
85
88
 
86
89
  ## `#event_on`
@@ -98,25 +101,41 @@ instance methods: `#morning_astronomical_twilight_time`,
98
101
  event = calculator.event_on(Date.new(2025, 5, 1))
99
102
 
100
103
  event.morning_astronomical_twilight_time
101
- # => 2025-05-01 01:17:18 UTC
104
+ # => 2025-05-01 01:16:21 UTC
102
105
 
103
106
  event.morning_nautical_twilight_time
104
- # => 2025-05-01 01:56:48 UTC
107
+ # => 2025-05-01 01:55:49 UTC
105
108
 
106
109
  event.evening_civil_twilight_time
107
- # => 2025-05-01 17:29:41 UTC
110
+ # => 2025-05-01 17:30:50 UTC
108
111
 
109
112
  event.evening_nautical_twilight_time
110
- # => 2025-05-01 18:06:08 UTC
113
+ # => 2025-05-01 18:07:27 UTC
111
114
 
112
115
  event.evening_astronomical_twilight_time
113
- # => 2025-05-01 18:45:38 UTC
116
+ # => 2025-05-01 18:47:10 UTC
114
117
  ```
115
118
 
119
+ ## Accuracy and polar regions
120
+
121
+ Each twilight time is the moment the Sun's centre, without refraction, reaches
122
+ the matching altitude, found by `Astronoby::AltitudeCrossingCalculator`. Times
123
+ are rounded to the second. Twilight that exists is returned even when the Sun
124
+ does not rise or set that day, as in a polar night. Use the [altitude crossing
125
+ calculator](altitude_crossings.md) directly for other altitudes or for unrounded
126
+ instants.
127
+
128
+ Solving for the real crossing costs more than approximating the Sun's position
129
+ from a single evaluation, as versions before 0.11.0 did, which could be off by
130
+ up to 30 seconds. `#event_on` takes around 7.5 milliseconds for one day, against
131
+ around 4 before.
132
+
116
133
  [Ephem page]: ephem.md
117
134
  [Observer page]: observer.md
118
135
 
119
136
  ## See also
137
+
138
+ - [Altitude Crossings](altitude_crossings.md) - for any altitude, unrounded
120
139
  - [Rise, Transit and Set Times](rise_transit_set_times.md) - for sun and moon events
121
140
  - [Observer](observer.md) - for location setup
122
141
  - [Ephemerides](ephem.md) - for data sources
@@ -1,6 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Represents an angle with radians as its internal representation.
5
+ # Provides conversions between radians, degrees, hours, and sexagesimal
6
+ # formats (DMS and HMS), as well as trigonometric operations.
7
+ #
8
+ # @example Create an angle from degrees
9
+ # angle = Astronoby::Angle.from_degrees(180)
10
+ # angle.radians # => Math::PI
11
+ #
12
+ # @example Create an angle from hours, minutes, seconds
13
+ # angle = Astronoby::Angle.from_hms(12, 30, 0)
14
+ #
4
15
  class Angle
5
16
  include Comparable
6
17
 
@@ -8,29 +19,42 @@ module Astronoby
8
19
  FORMATS = %i[dms hms].freeze
9
20
 
10
21
  class << self
22
+ # @return [Astronoby::Angle] a zero angle
11
23
  def zero
12
24
  new(0)
13
25
  end
14
26
 
27
+ # @param radians [Numeric] the angle in radians
28
+ # @return [Astronoby::Angle] a new Angle, normalized to (-2π, 2π)
15
29
  def from_radians(radians)
16
30
  normalized_radians = radians.remainder(Constants::RADIANS_PER_CIRCLE)
17
31
  new(normalized_radians)
18
32
  end
19
33
 
34
+ # @param degrees [Numeric] the angle in degrees
35
+ # @return [Astronoby::Angle] a new Angle
20
36
  def from_degrees(degrees)
21
37
  radians = degrees / Constants::PI_IN_DEGREES * Math::PI
22
38
  from_radians(radians)
23
39
  end
24
40
 
41
+ # @param arcseconds [Numeric] the angle in arcseconds
42
+ # @return [Astronoby::Angle] a new Angle
25
43
  def from_degree_arcseconds(arcseconds)
26
44
  from_dms(0, 0, arcseconds)
27
45
  end
28
46
 
47
+ # @param hours [Numeric] the angle in hour-angle hours
48
+ # @return [Astronoby::Angle] a new Angle
29
49
  def from_hours(hours)
30
50
  radians = hours * Constants::RADIAN_PER_HOUR
31
51
  from_radians(radians)
32
52
  end
33
53
 
54
+ # @param hour [Numeric] hours component
55
+ # @param minute [Numeric] minutes component
56
+ # @param second [Numeric] seconds component
57
+ # @return [Astronoby::Angle] a new Angle
34
58
  def from_hms(hour, minute, second)
35
59
  hours = hour +
36
60
  minute / Constants::MINUTES_PER_HOUR +
@@ -38,89 +62,118 @@ module Astronoby
38
62
  from_hours(hours)
39
63
  end
40
64
 
65
+ # @param degree [Numeric] degrees component (sign determines overall sign)
66
+ # @param minute [Numeric] arcminutes component
67
+ # @param second [Numeric] arcseconds component
68
+ # @return [Astronoby::Angle] a new Angle
41
69
  def from_dms(degree, minute, second)
42
70
  sign = degree.negative? ? -1 : 1
43
71
  degrees = degree.abs +
44
- minute / Constants::MINUTES_PER_HOUR +
45
- second / Constants::SECONDS_PER_HOUR
72
+ minute / Constants::ARCMINUTES_PER_DEGREE +
73
+ second / Constants::ARCSECONDS_PER_DEGREE
46
74
  from_degrees(sign * degrees)
47
75
  end
48
76
 
77
+ # @param ratio [Numeric] the sine value (-1..1)
78
+ # @return [Astronoby::Angle] the arcsine
49
79
  def asin(ratio)
50
80
  radians = Math.asin(ratio)
51
81
  from_radians(radians)
52
82
  end
53
83
 
84
+ # @param ratio [Numeric] the cosine value (-1..1)
85
+ # @return [Astronoby::Angle] the arccosine
54
86
  def acos(ratio)
55
87
  radians = Math.acos(ratio)
56
88
  from_radians(radians)
57
89
  end
58
90
 
91
+ # @param ratio [Numeric] the tangent value
92
+ # @return [Astronoby::Angle] the arctangent
59
93
  def atan(ratio)
60
94
  radians = Math.atan(ratio)
61
95
  from_radians(radians)
62
96
  end
63
97
  end
64
98
 
99
+ # @return [Numeric] the angle in radians
65
100
  attr_reader :radians
66
101
 
102
+ # @param radians [Numeric] the angle in radians
67
103
  def initialize(radians)
68
104
  @radians = radians
69
105
  freeze
70
106
  end
71
107
 
108
+ # @return [Float] the angle in degrees
72
109
  def degrees
73
110
  @radians * Constants::PI_IN_DEGREES / Math::PI
74
111
  end
75
112
 
113
+ # @return [Float] the angle in milliarcseconds
76
114
  def degree_milliarcseconds
77
115
  degrees * Constants::MILLIARCSECONDS_PER_DEGREE
78
116
  end
79
117
 
118
+ # @return [Float] the angle in hour-angle hours
80
119
  def hours
81
120
  @radians / Constants::RADIAN_PER_HOUR
82
121
  end
83
122
 
123
+ # @param other [Astronoby::Angle] angle to add
124
+ # @return [Astronoby::Angle] the sum
84
125
  def +(other)
85
126
  self.class.from_radians(radians + other.radians)
86
127
  end
87
128
 
129
+ # @param other [Astronoby::Angle] angle to subtract
130
+ # @return [Astronoby::Angle] the difference
88
131
  def -(other)
89
132
  self.class.from_radians(@radians - other.radians)
90
133
  end
91
134
 
135
+ # @return [Astronoby::Angle] the negated angle
92
136
  def -@
93
137
  self.class.from_radians(-@radians)
94
138
  end
95
139
 
140
+ # @return [Float] the sine of the angle
96
141
  def sin
97
142
  Math.sin(radians)
98
143
  end
99
144
 
145
+ # @return [Float] the cosine of the angle
100
146
  def cos
101
147
  Math.cos(radians)
102
148
  end
103
149
 
150
+ # @return [Float] the tangent of the angle
104
151
  def tan
105
152
  Math.tan(radians)
106
153
  end
107
154
 
155
+ # @return [Boolean] true if the angle is positive
108
156
  def positive?
109
157
  radians > 0
110
158
  end
111
159
 
160
+ # @return [Boolean] true if the angle is negative
112
161
  def negative?
113
162
  radians < 0
114
163
  end
115
164
 
165
+ # @return [Boolean] true if the angle is zero
116
166
  def zero?
117
167
  radians.zero?
118
168
  end
119
169
 
170
+ # @return [Integer] hash value
120
171
  def hash
121
172
  [radians, self.class].hash
122
173
  end
123
174
 
175
+ # @param other [Astronoby::Angle] angle to compare with
176
+ # @return [Integer, nil] -1, 0, or 1; nil if not comparable
124
177
  def <=>(other)
125
178
  return unless other.is_a?(self.class)
126
179
 
@@ -128,6 +181,12 @@ module Astronoby
128
181
  end
129
182
  alias_method :eql?, :==
130
183
 
184
+ # Formats the angle as a string in the given format.
185
+ #
186
+ # @param format [Symbol] :dms or :hms
187
+ # @param precision [Integer] decimal places for seconds
188
+ # @return [String] the formatted angle
189
+ # @raise [Astronoby::UnsupportedFormatError] if format is not :dms or :hms
131
190
  def str(format, precision: 4)
132
191
  case format
133
192
  when :dms then to_dms.format(precision: precision)
@@ -139,6 +198,7 @@ module Astronoby
139
198
  end
140
199
  end
141
200
 
201
+ # @return [Astronoby::Dms] the angle in degrees, arcminutes, arcseconds
142
202
  def to_dms
143
203
  sign = degrees.negative? ? "-" : "+"
144
204
  absolute_degrees = degrees.abs
@@ -156,6 +216,7 @@ module Astronoby
156
216
  Dms.new(sign, deg, minutes, seconds)
157
217
  end
158
218
 
219
+ # @return [Astronoby::Hms] the angle in hours, minutes, seconds
159
220
  def to_hms
160
221
  absolute_hours = hours.abs
161
222
  hrs = absolute_hours.floor
@@ -1,9 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Represents an angle in degrees, arcminutes, arcseconds (DMS) notation.
4
5
  class Dms
5
- attr_reader :sign, :degrees, :minutes, :seconds
6
+ # @return [String] "+" or "-"
7
+ attr_reader :sign
6
8
 
9
+ # @return [Integer] degrees component
10
+ attr_reader :degrees
11
+
12
+ # @return [Integer] arcminutes component
13
+ attr_reader :minutes
14
+
15
+ # @return [Float] arcseconds component
16
+ attr_reader :seconds
17
+
18
+ # @param sign [String] "+" or "-"
19
+ # @param degrees [Integer] degrees component
20
+ # @param minutes [Integer] arcminutes component
21
+ # @param seconds [Float] arcseconds component
7
22
  def initialize(sign, degrees, minutes, seconds)
8
23
  @sign = sign
9
24
  @degrees = degrees
@@ -11,6 +26,8 @@ module Astronoby
11
26
  @seconds = seconds
12
27
  end
13
28
 
29
+ # @param precision [Integer] decimal places for the seconds component
30
+ # @return [String] the formatted DMS string (e.g., "+45° 30′ 15.0000″")
14
31
  def format(precision: 4)
15
32
  "#{sign}#{degrees}° #{minutes}′ #{seconds.floor(precision)}″"
16
33
  end
@@ -1,15 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Represents an angle in hours, minutes, seconds (HMS) notation.
4
5
  class Hms
5
- attr_reader :hours, :minutes, :seconds
6
+ # @return [Integer] hours component
7
+ attr_reader :hours
6
8
 
9
+ # @return [Integer] minutes component
10
+ attr_reader :minutes
11
+
12
+ # @return [Float] seconds component
13
+ attr_reader :seconds
14
+
15
+ # @param hours [Integer] hours component
16
+ # @param minutes [Integer] minutes component
17
+ # @param seconds [Float] seconds component
7
18
  def initialize(hours, minutes, seconds)
8
19
  @hours = hours
9
20
  @minutes = minutes
10
21
  @seconds = seconds
11
22
  end
12
23
 
24
+ # @param precision [Integer] decimal places for the seconds component
25
+ # @return [String] the formatted HMS string (e.g., "12h 30m 45.0000s")
13
26
  def format(precision: 4)
14
27
  "#{hours}h #{minutes}m #{seconds.floor(precision)}s"
15
28
  end
@@ -1,18 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Represents an angular velocity with radians per second as its internal
5
+ # representation. Used primarily for stellar proper motion.
4
6
  class AngularVelocity
5
7
  include Comparable
6
8
 
7
9
  class << self
10
+ # @return [Astronoby::AngularVelocity] a zero angular velocity
8
11
  def zero
9
12
  new(0)
10
13
  end
11
14
 
15
+ # @param radians_per_second [Numeric] the angular velocity in rad/s
16
+ # @return [Astronoby::AngularVelocity] a new AngularVelocity
12
17
  def from_radians_per_second(radians_per_second)
13
18
  new(radians_per_second)
14
19
  end
15
20
 
21
+ # @param mas_per_year [Numeric] the angular velocity in mas/yr
22
+ # @return [Astronoby::AngularVelocity] a new AngularVelocity
16
23
  def from_milliarcseconds_per_year(mas_per_year)
17
24
  angle = Angle.from_degree_arcseconds(mas_per_year / 1000.0)
18
25
  radians_per_second = angle.radians / Constants::SECONDS_PER_JULIAN_YEAR
@@ -20,52 +27,66 @@ module Astronoby
20
27
  end
21
28
  end
22
29
 
30
+ # @return [Numeric] the angular velocity in radians per second
23
31
  attr_reader :radians_per_second
24
32
  alias_method :rps, :radians_per_second
25
33
 
34
+ # @param radians_per_second [Numeric] the angular velocity in rad/s
26
35
  def initialize(radians_per_second)
27
36
  @radians_per_second = radians_per_second
28
37
  freeze
29
38
  end
30
39
 
40
+ # @return [Float] the angular velocity in milliarcseconds per year
31
41
  def milliarcseconds_per_year
32
42
  angle = Angle.from_radians(@radians_per_second)
33
43
  angle.degree_milliarcseconds * Constants::SECONDS_PER_JULIAN_YEAR
34
44
  end
35
45
  alias_method :mas_per_year, :milliarcseconds_per_year
36
46
 
47
+ # @param other [Astronoby::AngularVelocity] angular velocity to add
48
+ # @return [Astronoby::AngularVelocity] the sum
37
49
  def +(other)
38
50
  self.class.from_radians_per_second(
39
51
  @radians_per_second + other.radians_per_second
40
52
  )
41
53
  end
42
54
 
55
+ # @param other [Astronoby::AngularVelocity] angular velocity to subtract
56
+ # @return [Astronoby::AngularVelocity] the difference
43
57
  def -(other)
44
58
  self.class.from_radians_per_second(
45
59
  @radians_per_second - other.radians_per_second
46
60
  )
47
61
  end
48
62
 
63
+ # @return [Astronoby::AngularVelocity] the negated angular velocity
49
64
  def -@
50
65
  self.class.from_radians_per_second(-@radians_per_second)
51
66
  end
52
67
 
68
+ # @return [Boolean] true if the angular velocity is positive
53
69
  def positive?
54
70
  @radians_per_second > 0
55
71
  end
56
72
 
73
+ # @return [Boolean] true if the angular velocity is negative
57
74
  def negative?
58
75
  @radians_per_second < 0
59
76
  end
60
77
 
78
+ # @return [Boolean] true if the angular velocity is zero
61
79
  def zero?
62
80
  @radians_per_second.zero?
63
81
  end
64
82
 
83
+ # @return [Integer] hash value
65
84
  def hash
66
85
  [@radians_per_second, self.class].hash
67
86
  end
68
87
 
88
+ # @param other [Astronoby::AngularVelocity] angular velocity to compare with
89
+ # @return [Integer, nil] -1, 0, or 1; nil if not comparable
69
90
  def <=>(other)
70
91
  return unless other.is_a?(self.class)
71
92
 
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Represents a deep-sky object (star, galaxy, nebula, etc.) with optional
5
+ # proper motion and parallax data.
4
6
  class DeepSkyObject
7
+ include Body
8
+
5
9
  # @param equatorial_coordinates [Astronoby::Coordinates::Equatorial]
6
10
  # Equatorial coordinates at epoch J2000.0
7
11
  # @param proper_motion_ra [Astronoby::AngularVelocity, nil] Proper motion in
@@ -37,7 +41,8 @@ module Astronoby
37
41
  proper_motion_ra: @proper_motion_ra,
38
42
  proper_motion_dec: @proper_motion_dec,
39
43
  parallax: @parallax,
40
- radial_velocity: @radial_velocity
44
+ radial_velocity: @radial_velocity,
45
+ deep_sky_object: self
41
46
  )
42
47
  end
43
48
  end
@@ -1,20 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Represents the computed position of a deep-sky object at a specific
5
+ # instant, providing astrometric, apparent, and topocentric reference frames.
4
6
  class DeepSkyObjectPosition
7
+ include Position
8
+
5
9
  DEFAULT_DISTANCE = Distance.from_parsecs(1e9)
6
10
 
7
- attr_reader :instant, :apparent
11
+ # @return [Astronoby::Instant] the time instant
12
+ attr_reader :instant
13
+
14
+ # @return [Astronoby::Apparent] the apparent reference frame
15
+ attr_reader :apparent
16
+
17
+ # @return [Astronoby::DeepSkyObject, nil] the body definition
18
+ attr_reader :body
8
19
 
9
20
  # @param instant [Astronoby::Instant] Instant of the observation
10
21
  # @param equatorial_coordinates [Astronoby::Coordinates::Equatorial]
11
22
  # Equatorial coordinates at epoch J2000.0
23
+ # @param ephem [::Ephem::SPK, nil] Ephemeris data source for Earth position
12
24
  # @param proper_motion_ra [Astronoby::AngularVelocity, nil] Proper motion in
13
25
  # right ascension
14
26
  # @param proper_motion_dec [Astronoby::AngularVelocity, nil] Proper motion
15
27
  # in declination
16
28
  # @param parallax [Astronoby::Angle, nil] Parallax angle
17
29
  # @param radial_velocity [Astronoby::Velocity, nil] Radial velocity
30
+ # @param deep_sky_object [Astronoby::DeepSkyObject, nil] the body definition
18
31
  def initialize(
19
32
  instant:,
20
33
  equatorial_coordinates:,
@@ -22,7 +35,8 @@ module Astronoby
22
35
  proper_motion_ra: nil,
23
36
  proper_motion_dec: nil,
24
37
  parallax: nil,
25
- radial_velocity: nil
38
+ radial_velocity: nil,
39
+ deep_sky_object: nil
26
40
  )
27
41
  @instant = instant
28
42
  @initial_equatorial_coordinates = equatorial_coordinates
@@ -30,6 +44,7 @@ module Astronoby
30
44
  @proper_motion_dec = proper_motion_dec
31
45
  @parallax = parallax
32
46
  @radial_velocity = radial_velocity
47
+ @body = deep_sky_object
33
48
  if ephem
34
49
  @earth_geometric = Earth.geometric(ephem: ephem, instant: @instant)
35
50
  end
@@ -42,24 +57,24 @@ module Astronoby
42
57
  instant: @instant,
43
58
  position: astrometric_position,
44
59
  velocity: astrometric_velocity,
45
- center_identifier: SolarSystemBody::EARTH,
46
- target_body: self
47
- )
48
- end
49
-
50
- def observed_by(observer)
51
- Topocentric.build_from_apparent(
52
- apparent: @apparent,
53
- observer: observer,
54
- instant: @instant,
55
- target_body: self
60
+ center: Center.geocentric,
61
+ target_body: body
56
62
  )
57
63
  end
58
64
 
59
65
  private
60
66
 
61
67
  def astrometric_position
62
- @astrometric_position ||= if use_stellar_propagation?
68
+ @astrometric_position ||=
69
+ if @earth_geometric
70
+ barycentric_position - @earth_geometric.position
71
+ else
72
+ barycentric_position
73
+ end
74
+ end
75
+
76
+ def barycentric_position
77
+ if use_stellar_propagation?
63
78
  stellar_propagation.position
64
79
  else
65
80
  astronomical_distance = DEFAULT_DISTANCE.meters
@@ -98,30 +113,12 @@ module Astronoby
98
113
  end
99
114
 
100
115
  def compute_apparent
101
- @apparent = if @earth_geometric
102
- Apparent.build_from_astrometric(
103
- instant: @instant,
104
- target_astrometric: astrometric,
105
- earth_geometric: @earth_geometric,
106
- target_body: self
107
- )
108
- else
109
- precession_matrix = Precession.matrix_for(@instant)
110
- nutation_matrix = Nutation.matrix_for(@instant)
111
- corrected_position = Distance.vector_from_meters(
112
- precession_matrix * nutation_matrix * astrometric.position.map(&:m)
113
- )
114
- corrected_velocity = Velocity.vector_from_mps(
115
- precession_matrix * nutation_matrix * astrometric.velocity.map(&:mps)
116
- )
117
- Apparent.new(
118
- position: corrected_position,
119
- velocity: corrected_velocity,
120
- instant: @instant,
121
- center_identifier: SolarSystemBody::EARTH,
122
- target_body: self
123
- )
124
- end
116
+ @apparent = Apparent.build_from_astrometric(
117
+ instant: @instant,
118
+ target_astrometric: astrometric,
119
+ earth_geometric: @earth_geometric,
120
+ target_body: body
121
+ )
125
122
  end
126
123
  end
127
124
  end