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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +203 -3
  5. data/README.md +69 -288
  6. data/UPGRADING.md +267 -0
  7. data/docs/README.md +196 -0
  8. data/docs/angles.md +137 -0
  9. data/docs/celestial_bodies.md +107 -0
  10. data/docs/configuration.md +98 -0
  11. data/docs/coordinates.md +167 -0
  12. data/docs/ephem.md +85 -0
  13. data/docs/equinoxes_solstices_times.md +31 -0
  14. data/docs/glossary.md +152 -0
  15. data/docs/instant.md +139 -0
  16. data/docs/moon_phases.md +79 -0
  17. data/docs/observer.md +65 -0
  18. data/docs/reference_frames.md +138 -0
  19. data/docs/rise_transit_set_times.md +119 -0
  20. data/docs/twilight_times.md +123 -0
  21. data/lib/astronoby/aberration.rb +56 -31
  22. data/lib/astronoby/angle.rb +20 -16
  23. data/lib/astronoby/angles/dms.rb +2 -2
  24. data/lib/astronoby/angles/hms.rb +2 -2
  25. data/lib/astronoby/bodies/earth.rb +62 -0
  26. data/lib/astronoby/bodies/jupiter.rb +28 -0
  27. data/lib/astronoby/bodies/mars.rb +28 -0
  28. data/lib/astronoby/bodies/mercury.rb +32 -0
  29. data/lib/astronoby/bodies/moon.rb +51 -298
  30. data/lib/astronoby/bodies/neptune.rb +32 -0
  31. data/lib/astronoby/bodies/saturn.rb +37 -0
  32. data/lib/astronoby/bodies/solar_system_body.rb +232 -0
  33. data/lib/astronoby/bodies/sun.rb +33 -214
  34. data/lib/astronoby/bodies/uranus.rb +16 -0
  35. data/lib/astronoby/bodies/venus.rb +36 -0
  36. data/lib/astronoby/cache.rb +188 -0
  37. data/lib/astronoby/configuration.rb +92 -0
  38. data/lib/astronoby/constants.rb +17 -2
  39. data/lib/astronoby/constellation.rb +12 -0
  40. data/lib/astronoby/constellations/data.rb +42 -0
  41. data/lib/astronoby/constellations/finder.rb +35 -0
  42. data/lib/astronoby/constellations/repository.rb +20 -0
  43. data/lib/astronoby/coordinates/ecliptic.rb +2 -37
  44. data/lib/astronoby/coordinates/equatorial.rb +28 -10
  45. data/lib/astronoby/coordinates/horizontal.rb +0 -46
  46. data/lib/astronoby/corrections/light_time_delay.rb +90 -0
  47. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  48. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  49. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  50. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  51. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  52. data/lib/astronoby/deflection.rb +187 -0
  53. data/lib/astronoby/distance.rb +9 -0
  54. data/lib/astronoby/ephem.rb +39 -0
  55. data/lib/astronoby/equinox_solstice.rb +22 -19
  56. data/lib/astronoby/errors.rb +4 -0
  57. data/lib/astronoby/events/moon_phases.rb +15 -13
  58. data/lib/astronoby/events/rise_transit_set_calculator.rb +376 -0
  59. data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
  60. data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
  61. data/lib/astronoby/events/twilight_calculator.rb +221 -0
  62. data/lib/astronoby/events/twilight_event.rb +28 -0
  63. data/lib/astronoby/events/twilight_events.rb +22 -115
  64. data/lib/astronoby/instant.rb +176 -0
  65. data/lib/astronoby/julian_date.rb +78 -0
  66. data/lib/astronoby/mean_obliquity.rb +24 -13
  67. data/lib/astronoby/nutation.rb +235 -42
  68. data/lib/astronoby/observer.rb +55 -0
  69. data/lib/astronoby/precession.rb +102 -18
  70. data/lib/astronoby/reference_frame.rb +50 -0
  71. data/lib/astronoby/reference_frames/apparent.rb +60 -0
  72. data/lib/astronoby/reference_frames/astrometric.rb +21 -0
  73. data/lib/astronoby/reference_frames/geometric.rb +20 -0
  74. data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
  75. data/lib/astronoby/reference_frames/topocentric.rb +72 -0
  76. data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
  77. data/lib/astronoby/true_obliquity.rb +3 -3
  78. data/lib/astronoby/util/maths.rb +70 -73
  79. data/lib/astronoby/util/time.rb +455 -32
  80. data/lib/astronoby/vector.rb +36 -0
  81. data/lib/astronoby/velocity.rb +116 -0
  82. data/lib/astronoby/version.rb +1 -1
  83. data/lib/astronoby.rb +33 -5
  84. metadata +117 -24
  85. data/.tool-versions +0 -1
  86. data/Gemfile +0 -5
  87. data/Gemfile.lock +0 -80
  88. data/benchmark/README.md +0 -131
  89. data/benchmark/benchmark.rb +0 -259
  90. data/benchmark/data/imcce.csv.zip +0 -0
  91. data/benchmark/data/sun_calc.csv.zip +0 -0
  92. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
  93. data/lib/astronoby/epoch.rb +0 -22
  94. data/lib/astronoby/events/observation_events.rb +0 -285
  95. data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -218
  96. data/lib/astronoby/util/astrodynamics.rb +0 -60
@@ -2,72 +2,265 @@
2
2
 
3
3
  module Astronoby
4
4
  class Nutation
5
- # Source:
6
- # Title: Practical Astronomy with your Calculator or Spreadsheet
7
- # Authors: Peter Duffett-Smith and Jonathan Zwart
8
- # Edition: Cambridge University Press
9
- # Chapter: 35 - Nutation
5
+ # IAU 2000B model corrections (in microarcseconds)
6
+ IAU2000B_DPSI_CORRECTION = -0.000135e7
7
+ IAU2000B_DEPS_CORRECTION = 0.000388e7
10
8
 
11
- def initialize(epoch)
12
- @epoch = epoch
13
- end
9
+ # Nutation terms from IAU 2000B model:
10
+ # 77 most significant terms from the IAU 2000A model
11
+ # 0..4: fundamental argument coefficients
12
+ # 5..7: longitude coefficients
13
+ # 8..10: obliquity coefficients
14
+ NUTATION_TERMS = [
15
+ [0, 0, 0, 0, 1, -172064161, -174666, 33386, 92052331, 9086, 15377],
16
+ [0, 0, 2, -2, 2, -13170906, -1675, -13696, 5730336, -3015, -4587],
17
+ [0, 0, 2, 0, 2, -2276413, -234, 2796, 978459, -485, 1374],
18
+ [0, 0, 0, 0, 2, 2074554, 207, -698, -897492, 470, -291],
19
+ [0, 1, 0, 0, 0, 1475877, -3633, 11817, 73871, -184, -1924],
20
+ [0, 1, 2, -2, 2, -516821, 1226, -524, 224386, -677, -174],
21
+ [1, 0, 0, 0, 0, 711159, 73, -872, -6750, 0, 358],
22
+ [0, 0, 2, 0, 1, -387298, -367, 380, 200728, 18, 318],
23
+ [1, 0, 2, 0, 2, -301461, -36, 816, 129025, -63, 367],
24
+ [0, -1, 2, -2, 2, 215829, -494, 111, -95929, 299, 132],
25
+ [0, 0, 2, -2, 1, 128227, 137, 181, -68982, -9, 39],
26
+ [-1, 0, 2, 0, 2, 123457, 11, 19, -53311, 32, -4],
27
+ [-1, 0, 0, 2, 0, 156994, 10, -168, -1235, 0, 82],
28
+ [1, 0, 0, 0, 1, 63110, 63, 27, -33228, 0, -9],
29
+ [-1, 0, 0, 0, 1, -57976, -63, -189, 31429, 0, -75],
30
+ [-1, 0, 2, 2, 2, -59641, -11, 149, 25543, -11, 66],
31
+ [1, 0, 2, 0, 1, -51613, -42, 129, 26366, 0, 78],
32
+ [-2, 0, 2, 0, 1, 45893, 50, 31, -24236, -10, 20],
33
+ [0, 0, 0, 2, 0, 63384, 11, -150, -1220, 0, 29],
34
+ [0, 0, 2, 2, 2, -38571, -1, 158, 16452, -11, 68],
35
+ [0, -2, 2, -2, 2, 32481, 0, 0, -13870, 0, 0],
36
+ [-2, 0, 0, 2, 0, -47722, 0, -18, 477, 0, -25],
37
+ [2, 0, 2, 0, 2, -31046, -1, 131, 13238, -11, 59],
38
+ [1, 0, 2, -2, 2, 28593, 0, -1, -12338, 10, -3],
39
+ [-1, 0, 2, 0, 1, 20441, 21, 10, -10758, 0, -3],
40
+ [2, 0, 0, 0, 0, 29243, 0, -74, -609, 0, 13],
41
+ [0, 0, 2, 0, 0, 25887, 0, -66, -550, 0, 11],
42
+ [0, 1, 0, 0, 1, -14053, -25, 79, 8551, -2, -45],
43
+ [-1, 0, 0, 2, 1, 15164, 10, 11, -8001, 0, -1],
44
+ [0, 2, 2, -2, 2, -15794, 72, -16, 6850, -42, -5],
45
+ [0, 0, -2, 2, 0, 21783, 0, 13, -167, 0, 13],
46
+ [1, 0, 0, -2, 1, -12873, -10, -37, 6953, 0, -14],
47
+ [0, -1, 0, 0, 1, -12654, 11, 63, 6415, 0, 26],
48
+ [-1, 0, 2, 2, 1, -10204, 0, 25, 5222, 0, 15],
49
+ [0, 2, 0, 0, 0, 16707, -85, -10, 168, -1, 10],
50
+ [1, 0, 2, 2, 2, -7691, 0, 44, 3268, 0, 19],
51
+ [-2, 0, 2, 0, 0, -11024, 0, -14, 104, 0, 2],
52
+ [0, 1, 2, 0, 2, 7566, -21, -11, -3250, 0, -5],
53
+ [0, 0, 2, 2, 1, -6637, -11, 25, 3353, 0, 14],
54
+ [0, -1, 2, 0, 2, -7141, 21, 8, 3070, 0, 4],
55
+ [0, 0, 0, 2, 1, -6302, -11, 2, 3272, 0, 4],
56
+ [1, 0, 2, -2, 1, 5800, 10, 2, -3045, 0, -1],
57
+ [2, 0, 2, -2, 2, 6443, 0, -7, -2768, 0, -4],
58
+ [-2, 0, 0, 2, 1, -5774, -11, -15, 3041, 0, -5],
59
+ [2, 0, 2, 0, 1, -5350, 0, 21, 2695, 0, 12],
60
+ [0, -1, 2, -2, 1, -4752, -11, -3, 2719, 0, -3],
61
+ [0, 0, 0, -2, 1, -4940, -11, -21, 2720, 0, -9],
62
+ [-1, -1, 0, 2, 0, 7350, 0, -8, -51, 0, 4],
63
+ [2, 0, 0, -2, 1, 4065, 0, 6, -2206, 0, 1],
64
+ [1, 0, 0, 2, 0, 6579, 0, -24, -199, 0, 2],
65
+ [0, 1, 2, -2, 1, 3579, 0, 5, -1900, 0, 1],
66
+ [1, -1, 0, 0, 0, 4725, 0, -6, -41, 0, 3],
67
+ [-2, 0, 2, 0, 2, -3075, 0, -2, 1313, 0, -1],
68
+ [3, 0, 2, 0, 2, -2904, 0, 15, 1233, 0, 7],
69
+ [0, -1, 0, 2, 0, 4348, 0, -10, -81, 0, 2],
70
+ [1, -1, 2, 0, 2, -2878, 0, 8, 1232, 0, 4],
71
+ [0, 0, 0, 1, 0, -4230, 0, 5, -20, 0, -2],
72
+ [-1, -1, 2, 2, 2, -2819, 0, 7, 1207, 0, 3],
73
+ [-1, 0, 2, 0, 0, -4056, 0, 5, 40, 0, -2],
74
+ [0, -1, 2, 2, 2, -2647, 0, 11, 1129, 0, 5],
75
+ [-2, 0, 0, 0, 1, -2294, 0, -10, 1266, 0, -4],
76
+ [1, 1, 2, 0, 2, 2481, 0, -7, -1062, 0, -3],
77
+ [2, 0, 0, 0, 1, 2179, 0, -2, -1129, 0, -2],
78
+ [-1, 1, 0, 1, 0, 3276, 0, 1, -9, 0, 0],
79
+ [1, 1, 0, 0, 0, -3389, 0, 5, 35, 0, -2],
80
+ [1, 0, 2, 0, 0, 3339, 0, -13, -107, 0, 1],
81
+ [-1, 0, 2, -2, 1, -1987, 0, -6, 1073, 0, -2],
82
+ [1, 0, 0, 0, 2, -1981, 0, 0, 854, 0, 0],
83
+ [-1, 0, 0, 1, 0, 4026, 0, -353, -553, 0, -139],
84
+ [0, 0, 2, 1, 2, 1660, 0, -5, -710, 0, -2],
85
+ [-1, 0, 2, 4, 2, -1521, 0, 9, 647, 0, 4],
86
+ [-1, 1, 0, 1, 1, 1314, 0, 0, -700, 0, 0],
87
+ [0, -2, 2, -2, 1, -1283, 0, 0, 672, 0, 0],
88
+ [1, 0, 2, 2, 1, -1331, 0, 8, 663, 0, 4],
89
+ [-2, 0, 2, 2, 2, 1383, 0, -2, -594, 0, -2],
90
+ [-1, 0, 0, 0, 2, 1405, 0, 4, -610, 0, 2],
91
+ [1, 1, 2, -2, 2, 1290, 0, 0, -556, 0, 0]
92
+ ]
14
93
 
15
- def self.for_ecliptic_longitude(epoch:)
16
- new(epoch).for_ecliptic_longitude
94
+ # @param instant [Astronoby::Instant] The time instant
95
+ # @return [Matrix] The nutation matrix
96
+ def self.matrix_for(instant)
97
+ new(instant: instant).matrix
17
98
  end
18
99
 
19
- def self.for_obliquity_of_the_ecliptic(epoch:)
20
- new(epoch).for_obliquity_of_the_ecliptic
100
+ # @param instant [Astronoby::Instant] The time instant
101
+ def initialize(instant:)
102
+ @instant = instant
21
103
  end
22
104
 
23
- def for_ecliptic_longitude
24
- Angle.from_dms(
25
- 0,
26
- 0,
27
- (
28
- -17.2 * moon_ascending_node_longitude.sin -
29
- 1.3 * Math.sin(2 * sun_mean_longitude.radians)
30
- )
105
+ # @return [Matrix] The nutation matrix
106
+ def matrix
107
+ mean_obliquity = MeanObliquity.at(@instant)
108
+ true_obliquity = mean_obliquity + nutation_in_obliquity
109
+ build_nutation_matrix(
110
+ mean_obliquity: mean_obliquity,
111
+ true_obliquity: true_obliquity,
112
+ psi: nutation_in_longitude
31
113
  )
32
114
  end
33
115
 
34
- def for_obliquity_of_the_ecliptic
35
- Angle.from_dms(
36
- 0,
37
- 0,
38
- (
39
- 9.2 * moon_ascending_node_longitude.cos +
40
- 0.5 * Math.cos(2 * sun_mean_longitude.radians)
41
- )
42
- )
116
+ # @return [Astronoby::Angle] Nutation angle in longitude
117
+ def nutation_in_longitude
118
+ iau2000b_angles.first
119
+ end
120
+
121
+ # @return [Astronoby::Angle] Nutation angle in obliquity
122
+ def nutation_in_obliquity
123
+ iau2000b_angles.last
43
124
  end
44
125
 
45
126
  private
46
127
 
128
+ def cache_key
129
+ @_cache_key ||= CacheKey.generate(:nutation, @instant)
130
+ end
131
+
132
+ def cache
133
+ Astronoby.cache
134
+ end
135
+
136
+ def iau2000a
137
+ a = fundamental_arguments
138
+
139
+ dpsi = 0.0
140
+ deps = 0.0
141
+
142
+ NUTATION_TERMS.each do |term|
143
+ # Extract the fundamental argument coefficients
144
+ arg_coef = term[0..4]
145
+
146
+ # Calculate the argument
147
+ arg = Util::Maths.dot_product(arg_coef, a.map(&:radians))
148
+
149
+ sin_arg = Math.sin(arg)
150
+ cos_arg = Math.cos(arg)
151
+
152
+ # Extract longitude coefficients
153
+ long_coef = term[5..7]
154
+
155
+ # Extract obliquity coefficients
156
+ obl_coef = term[8..10]
157
+
158
+ # Update dpsi using longitude coefficients
159
+ dpsi += long_coef[0] * sin_arg
160
+ dpsi += long_coef[1] * sin_arg * julian_centuries
161
+ dpsi += long_coef[2] * cos_arg
162
+
163
+ # Update deps using obliquity coefficients
164
+ deps += obl_coef[0] * cos_arg
165
+ deps += obl_coef[1] * cos_arg * julian_centuries
166
+ deps += obl_coef[2] * sin_arg
167
+ end
168
+
169
+ [dpsi, deps] # in microarcseconds
170
+ end
171
+
172
+ def iau2000b
173
+ dpsi, deps = iau2000a
174
+
175
+ # Apply corrections for IAU 2000B model
176
+ dpsi += IAU2000B_DPSI_CORRECTION
177
+ deps += IAU2000B_DEPS_CORRECTION
178
+
179
+ [dpsi, deps]
180
+ end
181
+
182
+ def iau2000b_angles
183
+ cache.fetch(cache_key) do
184
+ dpsi, deps = iau2000b
185
+ dpsi = Angle.from_degree_arcseconds(dpsi / 1e7)
186
+ deps = Angle.from_degree_arcseconds(deps / 1e7)
187
+
188
+ [dpsi, deps]
189
+ end
190
+ end
191
+
192
+ def build_nutation_matrix(mean_obliquity:, true_obliquity:, psi:)
193
+ cobm = mean_obliquity.cos
194
+ sobm = mean_obliquity.sin
195
+ cobt = true_obliquity.cos
196
+ sobt = true_obliquity.sin
197
+ cpsi = psi.cos
198
+ spsi = psi.sin
199
+
200
+ Matrix[
201
+ [cpsi, -spsi * cobm, -spsi * sobm],
202
+ [
203
+ spsi * cobt,
204
+ cpsi * cobm * cobt + sobm * sobt,
205
+ cpsi * sobm * cobt - cobm * sobt
206
+ ],
207
+ [
208
+ spsi * sobt,
209
+ cpsi * cobm * sobt - sobm * cobt,
210
+ cpsi * sobm * sobt + cobm * cobt
211
+ ]
212
+ ]
213
+ end
214
+
47
215
  def julian_centuries
48
- (@epoch - Epoch::J1900) / Constants::DAYS_PER_JULIAN_CENTURY
216
+ @julian_centuries ||=
217
+ (@instant.tt - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
49
218
  end
50
219
 
51
- def sun_mean_longitude
52
- Angle.from_degrees(
53
- (279.6967 + Constants::DEGREES_PER_CIRCLE * (centuries_a - centuries_a.to_i)) %
54
- Constants::DEGREES_PER_CIRCLE
220
+ # IAU 2006/2000A formula for the mean anomaly of the Moon
221
+ def mean_anomaly_moon
222
+ Angle.from_degree_arcseconds(
223
+ 485868.249036 + 1717915923.2178 * julian_centuries
55
224
  )
56
225
  end
57
226
 
58
- def moon_ascending_node_longitude
59
- Angle.from_degrees(
60
- (259.1833 - Constants::DEGREES_PER_CIRCLE * (centuries_b - centuries_b.to_i)) %
61
- Constants::DEGREES_PER_CIRCLE
227
+ # IAU 2006/2000A formula for the mean anomaly of the Sun
228
+ def mean_anomaly_sun
229
+ Angle.from_degree_arcseconds(
230
+ 1287104.79305 + 129596581.0481 * julian_centuries
62
231
  )
63
232
  end
64
233
 
65
- def centuries_a
66
- 100.002136 * julian_centuries
234
+ # IAU 2006/2000A formula for the mean argument of latitude of the Moon
235
+ def mean_argument_latitude_moon
236
+ Angle.from_degree_arcseconds(
237
+ 335779.526232 + 1739527262.8478 * julian_centuries
238
+ )
239
+ end
240
+
241
+ # IAU 2006/2000A formula for the mean elongation of the Moon from the Sun
242
+ def mean_elongation_moon_sun
243
+ Angle.from_degree_arcseconds(
244
+ 1072260.70369 + 1602961601.2090 * julian_centuries
245
+ )
67
246
  end
68
247
 
69
- def centuries_b
70
- 5.372617 * julian_centuries
248
+ # IAU 2006/2000A formula for the mean longitude of the ascending node of the
249
+ # Moon
250
+ def longitude_ascending_node_moon
251
+ Angle.from_degree_arcseconds(
252
+ 450160.398036 - 6962890.5431 * julian_centuries
253
+ )
254
+ end
255
+
256
+ def fundamental_arguments
257
+ l = mean_anomaly_moon
258
+ lp = mean_anomaly_sun
259
+ f = mean_argument_latitude_moon
260
+ d = mean_elongation_moon_sun
261
+ omega = longitude_ascending_node_moon
262
+
263
+ [l, lp, f, d, omega]
71
264
  end
72
265
  end
73
266
  end
@@ -42,6 +42,45 @@ module Astronoby
42
42
  @pressure = pressure || compute_pressure
43
43
  end
44
44
 
45
+ def geocentric_position
46
+ n = earth_prime_vertical_radius_of_curvature
47
+ x = (n + @elevation.m) * @latitude.cos * @longitude.cos
48
+ y = (n + @elevation.m) * @latitude.cos * @longitude.sin
49
+ z = (n * (1 - Constants::WGS84_ECCENTICITY_SQUARED) + @elevation.m) *
50
+ @latitude.sin
51
+ Distance.vector_from_meters([x, y, z])
52
+ end
53
+
54
+ def geocentric_velocity
55
+ r = projected_radius
56
+ vx = -Constants::EARTH_ANGULAR_VELOCITY_RAD_PER_S * r * @longitude.sin
57
+ vy = Constants::EARTH_ANGULAR_VELOCITY_RAD_PER_S * r * @longitude.cos
58
+ vz = 0.0
59
+ Velocity.vector_from_mps([vx, vy, vz])
60
+ end
61
+
62
+ def earth_fixed_rotation_matrix_for(instant)
63
+ dpsi = Nutation.new(instant: instant).nutation_in_longitude
64
+
65
+ mean_obliquity = MeanObliquity.at(instant)
66
+
67
+ gast = Angle.from_radians(
68
+ Angle.from_hours(instant.gmst).radians +
69
+ dpsi.radians * mean_obliquity.cos
70
+ )
71
+
72
+ earth_rotation_matrix = Matrix[
73
+ [gast.cos, -gast.sin, 0],
74
+ [gast.sin, gast.cos, 0],
75
+ [0, 0, 1]
76
+ ]
77
+
78
+ nutation_matrix = Nutation.matrix_for(instant)
79
+ precession_matrix = Precession.matrix_for(instant)
80
+
81
+ earth_rotation_matrix * nutation_matrix * precession_matrix
82
+ end
83
+
45
84
  def ==(other)
46
85
  return false unless other.is_a?(self.class)
47
86
 
@@ -84,5 +123,21 @@ module Astronoby
84
123
 
85
124
  Math.exp(-term1 / term2)
86
125
  end
126
+
127
+ def earth_prime_vertical_radius_of_curvature
128
+ Constants::WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS./(
129
+ Math.sqrt(
130
+ 1 -
131
+ Constants::WGS84_ECCENTICITY_SQUARED * @latitude.sin * @latitude.sin
132
+ )
133
+ )
134
+ end
135
+
136
+ def projected_radius
137
+ Math.sqrt(
138
+ geocentric_position.x.m * geocentric_position.x.m +
139
+ geocentric_position.y.m * geocentric_position.y.m
140
+ )
141
+ end
87
142
  end
88
143
  end
@@ -1,16 +1,76 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "matrix"
4
-
5
3
  module Astronoby
6
4
  class Precession
7
- def self.for_equatorial_coordinates(coordinates:, epoch:)
8
- new(coordinates, epoch).precess
5
+ def self.matrix_for(instant)
6
+ new(instant: instant).matrix
7
+ end
8
+
9
+ def initialize(instant:)
10
+ @instant = instant
11
+ end
12
+
13
+ def matrix
14
+ # Source:
15
+ # IAU resolution in 2006 in favor of the P03 astronomical model
16
+ # https://syrte.obspm.fr/iau2006/aa03_412_P03.pdf
17
+ # P(t) = R3(χA) R1(−ωA) R3(−ψA) R1(ϵ0)
18
+
19
+ cache.fetch(cache_key) do
20
+ # Precession in right ascension
21
+ psi_a = ((((
22
+ -0.0000000951 * t +
23
+ +0.000132851) * t +
24
+ -0.00114045) * t +
25
+ -1.0790069) * t +
26
+ +5038.481507) * t
27
+
28
+ # Precession in declination
29
+ omega_a = ((((
30
+ +0.0000003337 * t +
31
+ -0.000000467) * t +
32
+ -0.00772503) * t +
33
+ +0.0512623) * t +
34
+ -0.025754) * t +
35
+ eps0
36
+
37
+ # Precession of the ecliptic
38
+ chi_a = ((((
39
+ -0.0000000560 * t +
40
+ +0.000170663) * t +
41
+ -0.00121197) * t +
42
+ -2.3814292) * t +
43
+ +10.556403) * t
44
+
45
+ psi_a = Angle.from_degree_arcseconds(psi_a)
46
+ omega_a = Angle.from_degree_arcseconds(omega_a)
47
+ chi_a = Angle.from_degree_arcseconds(chi_a)
48
+
49
+ r3_psi = rotation_z(-psi_a)
50
+ r1_omega = rotation_x(-omega_a)
51
+ r3_chi = rotation_z(chi_a)
52
+ r1_eps0 = rotation_x(MeanObliquity.obliquity_of_reference)
53
+
54
+ r3_chi * r1_omega * r3_psi * r1_eps0
55
+ end
9
56
  end
10
57
 
11
- def initialize(coordinates, epoch)
12
- @coordinates = coordinates
13
- @epoch = epoch
58
+ def rotation_x(angle)
59
+ c, s = angle.cos, angle.sin
60
+ Matrix[
61
+ [1, 0, 0],
62
+ [0, c, s],
63
+ [0, -s, c]
64
+ ]
65
+ end
66
+
67
+ def rotation_z(angle)
68
+ c, s = angle.cos, angle.sin
69
+ Matrix[
70
+ [c, s, 0],
71
+ [-s, c, 0],
72
+ [0, 0, 1]
73
+ ]
14
74
  end
15
75
 
16
76
  # Source:
@@ -18,14 +78,19 @@ module Astronoby
18
78
  # Authors: Peter Duffett-Smith and Jonathan Zwart
19
79
  # Edition: Cambridge University Press
20
80
  # Chapter: 34 - Precession
21
- def precess
22
- matrix_a = matrix_for_epoch(@coordinates.epoch)
23
- matrix_b = matrix_for_epoch(@epoch).transpose
81
+
82
+ def self.for_equatorial_coordinates(coordinates:, epoch:)
83
+ precess(coordinates, epoch)
84
+ end
85
+
86
+ def self.precess(coordinates, epoch)
87
+ matrix_a = matrix_for_epoch(coordinates.epoch)
88
+ matrix_b = matrix_for_epoch(epoch).transpose
24
89
 
25
90
  vector = Vector[
26
- @coordinates.right_ascension.cos * @coordinates.declination.cos,
27
- @coordinates.right_ascension.sin * @coordinates.declination.cos,
28
- @coordinates.declination.sin
91
+ coordinates.right_ascension.cos * coordinates.declination.cos,
92
+ coordinates.right_ascension.sin * coordinates.declination.cos,
93
+ coordinates.declination.sin
29
94
  ]
30
95
 
31
96
  s = matrix_a * vector
@@ -38,14 +103,12 @@ module Astronoby
38
103
  Angle.atan(w[1] / w[0])
39
104
  ),
40
105
  declination: Angle.asin(w[2]),
41
- epoch: @epoch
106
+ epoch: epoch
42
107
  )
43
108
  end
44
109
 
45
- private
46
-
47
- def matrix_for_epoch(epoch)
48
- t = (epoch - Epoch::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
110
+ def self.matrix_for_epoch(epoch)
111
+ t = (epoch - JulianDate::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
49
112
 
50
113
  zeta = Angle.from_degrees(
51
114
  0.6406161 * t + 0.0000839 * t * t + 0.000005 * t * t * t
@@ -82,5 +145,26 @@ module Astronoby
82
145
  ]
83
146
  ]
84
147
  end
148
+
149
+ private
150
+
151
+ def cache_key
152
+ @_cache_key ||= CacheKey.generate(:precession, @instant)
153
+ end
154
+
155
+ def cache
156
+ Astronoby.cache
157
+ end
158
+
159
+ def t
160
+ @t ||= Rational(
161
+ @instant.tdb - JulianDate::DEFAULT_EPOCH,
162
+ Constants::DAYS_PER_JULIAN_CENTURY
163
+ )
164
+ end
165
+
166
+ def eps0
167
+ @eps0 ||= MeanObliquity.obliquity_of_reference_in_arcseconds
168
+ end
85
169
  end
86
170
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class ReferenceFrame
5
+ attr_reader :position,
6
+ :velocity,
7
+ :instant,
8
+ :center_identifier,
9
+ :target_body
10
+
11
+ def initialize(
12
+ position:,
13
+ velocity:,
14
+ instant:,
15
+ center_identifier:,
16
+ target_body:
17
+ )
18
+ @position = position
19
+ @velocity = velocity
20
+ @instant = instant
21
+ @center_identifier = center_identifier
22
+ @target_body = target_body
23
+ end
24
+
25
+ def equatorial
26
+ @equatorial ||= begin
27
+ return Coordinates::Equatorial.zero if distance.zero?
28
+
29
+ Coordinates::Equatorial.from_position_vector(@position)
30
+ end
31
+ end
32
+
33
+ def ecliptic
34
+ @ecliptic ||= begin
35
+ return Coordinates::Ecliptic.zero if distance.zero?
36
+
37
+ j2000 = Instant.from_terrestrial_time(JulianDate::J2000)
38
+ equatorial.to_ecliptic(instant: j2000)
39
+ end
40
+ end
41
+
42
+ def distance
43
+ @distance ||= begin
44
+ return Distance.zero if @position.zero?
45
+
46
+ @position.magnitude
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Apparent < ReferenceFrame
5
+ def self.build_from_astrometric(
6
+ instant:,
7
+ target_astrometric:,
8
+ earth_geometric:,
9
+ target_body:
10
+ )
11
+ position = target_astrometric.position
12
+ velocity = target_astrometric.velocity
13
+ precession_matrix = Precession.matrix_for(instant)
14
+ nutation_matrix = Nutation.matrix_for(instant)
15
+
16
+ corrected_position = Distance.vector_from_meters(
17
+ precession_matrix * nutation_matrix * position.map(&:m)
18
+ )
19
+ corrected_position = Aberration.new(
20
+ astrometric_position: corrected_position,
21
+ observer_velocity: earth_geometric.velocity
22
+ ).corrected_position
23
+ # In theory, here we should also apply light deflection. However, so far
24
+ # the deflection algorithm hasn't shown any significant changes to the
25
+ # apparent position. Therefore, for now, we are saving some computation
26
+ # time by not applying it, and we will investigate if the algorithm is
27
+ # correct or if the deflection is indeed negligible.
28
+
29
+ corrected_velocity = Velocity.vector_from_mps(
30
+ precession_matrix * nutation_matrix * velocity.map(&:mps)
31
+ )
32
+
33
+ new(
34
+ position: corrected_position,
35
+ velocity: corrected_velocity,
36
+ instant: instant,
37
+ center_identifier: SolarSystemBody::EARTH,
38
+ target_body: target_body
39
+ )
40
+ end
41
+
42
+ def ecliptic
43
+ @ecliptic ||= begin
44
+ return Coordinates::Ecliptic.zero if distance.zero?
45
+
46
+ equatorial.to_ecliptic(instant: @instant)
47
+ end
48
+ end
49
+
50
+ def angular_diameter
51
+ @angular_radius ||= begin
52
+ return Angle.zero if @position.zero?
53
+
54
+ Angle.from_radians(
55
+ Math.atan(@target_body.class::EQUATORIAL_RADIUS.m / distance.m) * 2
56
+ )
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Astrometric < ReferenceFrame
5
+ def self.build_from_geometric(
6
+ instant:,
7
+ earth_geometric:,
8
+ light_time_corrected_position:,
9
+ light_time_corrected_velocity:,
10
+ target_body:
11
+ )
12
+ new(
13
+ position: light_time_corrected_position - earth_geometric.position,
14
+ velocity: light_time_corrected_velocity - earth_geometric.velocity,
15
+ instant: instant,
16
+ center_identifier: SolarSystemBody::EARTH,
17
+ target_body: target_body
18
+ )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Geometric < ReferenceFrame
5
+ def initialize(
6
+ position:,
7
+ velocity:,
8
+ instant:,
9
+ target_body:
10
+ )
11
+ super(
12
+ position: position,
13
+ velocity: velocity,
14
+ instant: instant,
15
+ center_identifier: SolarSystemBody::SOLAR_SYSTEM_BARYCENTER,
16
+ target_body: target_body
17
+ )
18
+ end
19
+ end
20
+ end