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
data/docs/README.md CHANGED
@@ -62,10 +62,10 @@ You can learn more about reference frames and positions on the
62
62
 
63
63
  ```rb
64
64
  topocentric.horizontal.azimuth.str(:dms)
65
- # => "+175° 34′ 28.2724″"
65
+ # => "+175° 34′ 33.2353″"
66
66
 
67
67
  topocentric.horizontal.altitude.str(:dms)
68
- # => "+64° 22′ 58.1084″"
68
+ # => "+64° 22′ 58.229″"
69
69
  ```
70
70
 
71
71
  You can learn more about coordinates on the [Coordinates page].
@@ -108,26 +108,47 @@ calculator = Astronoby::TwilightCalculator.new(
108
108
  event = calculator.event_on(Date.new(2025, 5, 1))
109
109
 
110
110
  event.morning_astronomical_twilight_time
111
- # => 2025-05-01 02:17:28 UTC
111
+ # => 2025-05-01 02:17:02 UTC
112
112
 
113
113
  event.morning_nautical_twilight_time
114
- # => 2025-05-01 03:10:17 UTC
114
+ # => 2025-05-01 03:09:39 UTC
115
115
 
116
116
  event.morning_civil_twilight_time
117
- # => 2025-05-01 03:55:17 UTC
117
+ # => 2025-05-01 03:54:36 UTC
118
118
 
119
119
  event.evening_civil_twilight_time
120
- # => 2025-05-01 19:40:12 UTC
120
+ # => 2025-05-01 19:41:58 UTC
121
121
 
122
122
  event.evening_nautical_twilight_time
123
- # => 2025-05-01 20:25:12 UTC
123
+ # => 2025-05-01 20:27:15 UTC
124
124
 
125
125
  event.evening_astronomical_twilight_time
126
- # => 2025-05-01 21:18:01 UTC
126
+ # => 2025-05-01 21:20:32 UTC
127
127
  ```
128
128
 
129
129
  You can learn more about this calculator on the [Twilight times page].
130
130
 
131
+ ## Find when a body reaches any altitude
132
+
133
+ ```rb
134
+ calculator = Astronoby::AltitudeCrossingCalculator.new(
135
+ body: Astronoby::Sun,
136
+ observer: observer,
137
+ ephem: ephem
138
+ )
139
+
140
+ crossing = calculator.crossings_between(
141
+ Time.utc(2025, 5, 1),
142
+ Time.utc(2025, 5, 2),
143
+ altitude: Astronoby::Angle.from_degrees(30)
144
+ ).first
145
+
146
+ crossing.direction
147
+ # => :rising
148
+ ```
149
+
150
+ You can learn more about this calculator on the [Altitude crossings page].
151
+
131
152
  ## Moon phases
132
153
 
133
154
  You can either get all the major Moon phases that will happen in a month, or get
@@ -141,7 +162,7 @@ may_2024_phases.each { puts "#{_1.phase}: #{_1.time}" }
141
162
  # new_moon: 2024-05-08 03:21:56 UTC
142
163
  # first_quarter: 2024-05-15 11:48:02 UTC
143
164
  # full_moon: 2024-05-23 13:53:12 UTC
144
- # last_quarter: 2024-05-30 17:12:43 UTC
165
+ # last_quarter: 2024-05-30 17:12:42 UTC
145
166
  ```
146
167
 
147
168
  ```rb
@@ -150,14 +171,53 @@ instant = Astronoby::Instant.from_time(time)
150
171
  moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
151
172
 
152
173
  moon.illuminated_fraction.round(2)
153
- # => 0.15
174
+ # => 0.95
154
175
 
155
176
  moon.current_phase_fraction.round(2)
156
- # => 0.11
177
+ # => 0.59
157
178
  ```
158
179
 
159
180
  You can learn more about phases on the [Moon phases page].
160
181
 
182
+ ## Lunar observation
183
+
184
+ ```rb
185
+ time = Time.utc(2025, 3, 1)
186
+ instant = Astronoby::Instant.from_time(time)
187
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
188
+
189
+ moon.libration.longitude.str(:dms)
190
+ # => "-2° 5′ 4.4219″"
191
+
192
+ moon.libration.latitude.str(:dms)
193
+ # => "+0° 28′ 45.9616″"
194
+
195
+ moon.position_angle_of_axis.str(:dms)
196
+ # => "-21° 48′ 46.657″"
197
+
198
+ moon.bright_limb_position_angle.str(:dms)
199
+ # => "+248° 1′ 57.9467″"
200
+ ```
201
+
202
+ You can learn more about libration, axis and limb angles on the
203
+ [Lunar observation page].
204
+
205
+ ## Lunar eclipses
206
+
207
+ ```rb
208
+ eclipses = Astronoby::Moon.eclipse_events(
209
+ ephem: ephem,
210
+ start_time: Time.utc(2025, 1, 1),
211
+ end_time: Time.utc(2026, 1, 1)
212
+ )
213
+
214
+ eclipses.map { |eclipse| [eclipse.instant.to_time, eclipse.kind] }
215
+ # => [[2025-03-14 06:58:46 UTC, :total], [2025-09-07 18:11:49 UTC, :total]]
216
+ ```
217
+
218
+ You can learn more about umbral and penumbral magnitudes, phases and contact
219
+ times on the [Lunar eclipses page].
220
+
161
221
  ## Equinox and solstice times
162
222
 
163
223
  ```rb
@@ -166,18 +226,39 @@ Astronoby::EquinoxSolstice.march_equinox(2025, ephem)
166
226
  # => 2025-03-20 09:01:29 UTC
167
227
 
168
228
  Astronoby::EquinoxSolstice.june_solstice(2025, ephem)
169
- # => 2025-06-21 02:42:19 UTC
229
+ # => 2025-06-21 02:42:16 UTC
170
230
 
171
231
  Astronoby::EquinoxSolstice.september_equinox(2025, ephem)
172
- # => 2025-09-22 18:19:22 UTC
232
+ # => 2025-09-22 18:19:21 UTC
173
233
 
174
234
  Astronoby::EquinoxSolstice.december_solstice(2025, ephem)
175
- # => 2025-12-21 15:03:03 UTC
235
+ # => 2025-12-21 15:03:05 UTC
176
236
  ```
177
237
 
178
238
  You can learn more about equinoxes and solstices on the
179
239
  [Equinoxes and solstices times page].
180
240
 
241
+ ## Planetary phenomena
242
+
243
+ ```rb
244
+ Astronoby::Mars.opposition_events(
245
+ ephem: ephem,
246
+ start_time: Time.utc(2025, 1, 1),
247
+ end_time: Time.utc(2026, 1, 1)
248
+ ).first.instant.to_time
249
+ # => 2025-01-16 02:38:35 UTC
250
+
251
+ Astronoby::Venus.greatest_elongation_events(
252
+ ephem: ephem,
253
+ start_time: Time.utc(2025, 1, 1),
254
+ end_time: Time.utc(2026, 1, 1)
255
+ ).first.angle.degrees.round(1)
256
+ # => 47.2
257
+ ```
258
+
259
+ You can learn more about conjunctions, oppositions and greatest elongations on
260
+ the [Planetary phenomena page].
261
+
181
262
  ## Deep-sky objects
182
263
 
183
264
  It is possible to manipulate any deep-sky possible, given equatorial coordinates
@@ -200,13 +281,17 @@ vega = Astronoby::DeepSkyObject.new(equatorial_coordinates: vega_j2000)
200
281
  vega_position = vega.at(instant)
201
282
 
202
283
  vega_position.apparent.equatorial.right_ascension.str(:hms)
203
- # => "18h 36m 56.3363s"
284
+ # => "18h 37m 48.2804s"
204
285
  ```
205
286
 
206
- You can learn more about deep-sky objects on the [Deep-sky Bodies page].
287
+ You can learn more about deep-sky objects, and about converting many points to
288
+ horizontal coordinates at once with `HorizontalTransformation`, on the
289
+ [Deep-sky Bodies page].
207
290
 
208
291
  ## See also
292
+
209
293
  - [Glossary](glossary.md) - for astronomical and technical terms
294
+ - [IERS Data](iers.md) - for Delta T and GMST data management
210
295
  - [Configuration](configuration.md) - for performance tuning
211
296
  - [Performance Benchmarks](../benchmarks/README.md) - for testing improvements
212
297
 
@@ -219,6 +304,10 @@ You can learn more about deep-sky objects on the [Deep-sky Bodies page].
219
304
  [Coordinates page]: coordinates.md
220
305
  [Rise, transit and setting times page]: rise_transit_set_times.md
221
306
  [Twilight times page]: twilight_times.md
307
+ [Altitude crossings page]: altitude_crossings.md
222
308
  [Moon phases page]: moon_phases.md
309
+ [Lunar observation page]: lunar_observation.md
310
+ [Lunar eclipses page]: lunar_eclipses.md
223
311
  [Equinoxes and solstices times page]: equinoxes_solstices_times.md
312
+ [Planetary phenomena page]: planetary_phenomena.md
224
313
  [Deep-sky Bodies page]: deep_sky_bodies.md
@@ -0,0 +1,92 @@
1
+ # Altitude crossings
2
+
3
+ `Astronoby::AltitudeCrossingCalculator` finds when a body reaches any altitude above the observer's horizon, positive or negative, with instants that are not rounded. [Twilight times](twilight_times.md) are built on it.
4
+
5
+ ## Initialization
6
+
7
+ It takes as key arguments:
8
+
9
+ - `body`: the body to track, for example `Astronoby::Sun`
10
+ - `observer` (`Astronoby::Observer`): location on Earth of the observer
11
+ - `ephem`: ephemeris to provide the initial raw data
12
+ - `refraction` (optional, `false` by default): whether atmospheric refraction is applied to the altitude, `false` being the twilight convention
13
+ - `reference` (optional, `:center` by default): which point of the body reaches the altitude, `:center`, `:upper_limb` or `:lower_limb`. The limbs use the body's true semi-diameter and need a solar system body.
14
+
15
+ ```rb
16
+ calculator = Astronoby::AltitudeCrossingCalculator.new(
17
+ body: Astronoby::Sun,
18
+ observer: observer,
19
+ ephem: ephem
20
+ )
21
+ ```
22
+
23
+ ## `#crossings_between`
24
+
25
+ It returns the `Astronoby::AltitudeCrossing` objects in a time range, sorted by time.
26
+
27
+ ```rb
28
+ crossings = calculator.crossings_between(
29
+ Time.utc(2026, 10, 23),
30
+ Time.utc(2026, 10, 24),
31
+ altitude: Astronoby::Angle.from_degrees(-17.5)
32
+ )
33
+
34
+ crossing = crossings.first
35
+
36
+ crossing.instant # => Astronoby::Instant, unrounded
37
+ crossing.direction # => :rising or :setting
38
+ crossing.grazing? # => true when the body only just reaches the altitude
39
+ ```
40
+
41
+ A crossing is `:rising` when the altitude increases through the target. It is `grazing?` when the target is within 0.05° of the body's highest or lowest altitude, where the instant is sensitive to small errors.
42
+
43
+ ## `#multiple_crossings_between`
44
+
45
+ It does the same for several altitudes at once and returns one array of crossings per altitude, in the order given. The culminations and the computed positions are shared between the altitudes, which makes it much cheaper than one call per altitude.
46
+
47
+ ```rb
48
+ civil, nautical = calculator.multiple_crossings_between(
49
+ start_time,
50
+ end_time,
51
+ altitudes: [Astronoby::Angle.from_degrees(-6), Astronoby::Angle.from_degrees(-12)]
52
+ )
53
+ ```
54
+
55
+ ## `#crossing_on`
56
+
57
+ It returns the first crossing of a given direction on a local day, or `nil`.
58
+
59
+ ```rb
60
+ calculator.crossing_on(
61
+ Date.new(2026, 10, 23),
62
+ altitude: Astronoby::Angle.from_degrees(-17.5),
63
+ direction: :setting,
64
+ utc_offset: "+02:00"
65
+ )
66
+ ```
67
+
68
+ ## `#extremes_between`
69
+
70
+ It returns the upper and lower culminations in a time range, as `Astronoby::AltitudeExtreme` objects with `#kind`, `#instant` and `#altitude`. They are useful to explain why a crossing is absent: the body never got as high as, or as low as, the target.
71
+
72
+ ```rb
73
+ calculator.extremes_between(start_time, end_time)
74
+ ```
75
+
76
+ ## Accuracy
77
+
78
+ The altitude at the returned instant is within 10⁻⁵° of the target, and crossings agree with independent tools. The Sun's centre, without refraction, was compared on 2026-10-23 in Paris, 2026-03-20 in Makkah and 2026-12-21 at 60° N 10° E and 75° N 10° E, at -6°, -12° and -18° (22 crossings, observer at sea level):
79
+
80
+ | Tool | Resolution | Largest difference with Astronoby |
81
+ | --- | --- | --- |
82
+ | [IMCCE OPALE](https://opale.imcce.fr) | 1 s | 0.6 s |
83
+ | [JPL Horizons](https://ssd.jpl.nasa.gov/horizons/) | 1 min table, interpolated | 0.10 s |
84
+ | [Skyfield](https://rhodesmills.github.io/skyfield/) on the same DE440s excerpt | 1 ms | 0.19 s |
85
+ | [USNO](https://aa.usno.navy.mil/data/api) | 1 min, civil twilight only | within its rounding |
86
+
87
+ The Skyfield differences are a time-scale effect rather than a geometric one: they are 0.00 s in March, 0.14 s in October and 0.18 s in December, which is the difference between Skyfield's built-in ΔT prediction and the IERS value Astronoby uses on those days. The remaining differences with Horizons and IMCCE come from the ephemerides (DE441 and INPOP19A against DE440s). The comparison also includes the polar night at 75° N, where the Sun does not rise but reaches -12° and -18°, and where the Sun never reaches -6°, as every tool agrees.
88
+
89
+ ## See also
90
+
91
+ - [Twilight Times](twilight_times.md)
92
+ - [Rise, Transit and Set Times](rise_transit_set_times.md)
data/docs/angles.md CHANGED
@@ -131,6 +131,7 @@ angle1.negative? # => false
131
131
  ```
132
132
 
133
133
  ## See also
134
+
134
135
  - [Coordinates](coordinates.md) - for using angles in coordinate systems
135
136
  - [Observer](observer.md) - for latitude and longitude
136
137
  - [Reference Frames](reference_frames.md) - for position calculations
@@ -20,11 +20,20 @@ Astronoby.configuration.cache_enabled = true
20
20
 
21
21
  Once caching is enabled, it will be used for the following parts of the library
22
22
  with default precision values:
23
- * [Geometric positions]
24
- * [Topocentric positions] when computing rising/transit/setting times
25
- * [Moon phases]
26
- * Nutation
27
- * Precession
23
+
24
+ - [Geometric positions]
25
+ - [Topocentric positions] when computing rising/transit/setting times
26
+ - [Moon phases]
27
+ - Nutation
28
+ - Precession
29
+ - Greenwich mean and apparent sidereal times, which also cover the local
30
+ apparent sidereal time
31
+
32
+ Sidereal times are cached for the exact UTC time, with no rounding. The cache
33
+ helps when the same instant is used many times, for example when converting
34
+ many points to horizontal coordinates for one observer. Calling
35
+ `DeepSkyObject#at(instant).observed_by(observer).horizontal` repeatedly at one
36
+ instant is about 2.5 times faster with the cache enabled.
28
37
 
29
38
  ## Cache precision
30
39
 
@@ -35,10 +44,11 @@ However, users may want to improve performance even more at the cost of some
35
44
  precision.
36
45
 
37
46
  It is possible to change the precision value for the following:
38
- * Geometric position, default: `9`
39
- * Topocentric position, default: `5`
40
- * Nutation, default: `2`
41
- * Precession, default: `2`
47
+
48
+ - Geometric position, default: `9`
49
+ - Topocentric position, default: `5`
50
+ - Nutation, default: `2`
51
+ - Precession, default: `2`
42
52
 
43
53
  ### Precision value
44
54
 
@@ -49,14 +59,14 @@ Let's take the example of `1`. Rounding a Julian Day with only one digit means
49
59
  that all times within 8640 seconds will be rounded to the same instant, which
50
60
  means a maximum rounding of 2.4 hours.
51
61
 
52
- * `1`: 2.4 hours
53
- * `2`: 14.4 minutes
54
- * `3`: 86.4 seconds
55
- * `4`: 8.6 seconds
56
- * `5`: 0.86 seconds
57
- * `6`: 0.086 seconds
58
- * `7`: 0.0086 seconds
59
- * ...
62
+ - `1`: 2.4 hours
63
+ - `2`: 14.4 minutes
64
+ - `3`: 86.4 seconds
65
+ - `4`: 8.6 seconds
66
+ - `5`: 0.86 seconds
67
+ - `6`: 0.086 seconds
68
+ - `7`: 0.0086 seconds
69
+ - ...
60
70
 
61
71
  ### Setting custom precision
62
72
 
@@ -92,6 +102,7 @@ Astronoby.cache.max_size
92
102
  [Julian Day]: https://en.wikipedia.org/wiki/Julian_day
93
103
 
94
104
  ## See also
105
+
95
106
  - [Reference Frames](reference_frames.md) - for understanding position calculations
96
107
  - [Ephemerides](ephem.md) - for data sources
97
108
  - [Performance Benchmarks](benchmarks/README.md) - for testing improvements
data/docs/coordinates.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # Coordinates
2
2
 
3
- Astronoby provides three different types of coordinates:
3
+ Astronoby provides four types of coordinates:
4
4
 
5
- * Equatorial
6
- * Ecliptic
7
- * Horizontal
5
+ - Equatorial
6
+ - Ecliptic
7
+ - Horizontal
8
+ - Geodetic
8
9
 
9
10
  Equatorial and ecliptic coordinates are available for each [reference frame],
10
- while horizontal coordinates are only available for a topocentric position.
11
+ horizontal coordinates are only available for a topocentric position, and
12
+ geodetic coordinates are used for Earth-surface positions (ECEF ↔ WGS-84).
11
13
 
12
14
  ## Equatorial
13
15
 
@@ -36,7 +38,7 @@ saturn = Astronoby::Saturn.new(ephem: ephem, instant: instant)
36
38
  equatorial = saturn.apparent.equatorial
37
39
 
38
40
  equatorial.right_ascension.str(:hms)
39
- # => "20h 45m 2.6702s"
41
+ # => "20h 45m 2.6681s"
40
42
  ```
41
43
 
42
44
  You can learn more about angles on the [Angle page].
@@ -49,7 +51,7 @@ is at -90°.
49
51
 
50
52
  ```rb
51
53
  equatorial.declination.str(:dms)
52
- # => "-18° 46′ 16.1226″"
54
+ # => "-18° 46′ 16.0931″"
53
55
  ```
54
56
 
55
57
  ### Hour angle
@@ -63,7 +65,7 @@ time. By convention, the hour angle is usually displayed in hours.
63
65
  longitude = Astronoby::Angle.from_degrees(2)
64
66
 
65
67
  equatorial.compute_hour_angle(longitude: longitude, time: time).str(:hms)
66
- # => "23h 27m 54.9585s"
68
+ # => "23h 27m 54.1924s"
67
69
  ```
68
70
 
69
71
  ## Ecliptic
@@ -101,7 +103,7 @@ saturn = Astronoby::Saturn.new(ephem: ephem, instant: instant)
101
103
  ecliptic = saturn.apparent.ecliptic
102
104
 
103
105
  ecliptic.latitude.str(:dms)
104
- # => "-0° 41′ 27.5439″"
106
+ # => "-0° 41′ 27.5077″"
105
107
  ```
106
108
 
107
109
  ### Longitude
@@ -111,7 +113,7 @@ ecliptic towards the north (positive) or south (negative) ecliptic pole.
111
113
 
112
114
  ```rb
113
115
  ecliptic.longitude.str(:dms)
114
- # => "+308° 38′ 33.1744″"
116
+ # => "+308° 38′ 33.1535″"
115
117
  ```
116
118
 
117
119
  ## Horizontal
@@ -142,7 +144,7 @@ saturn = Astronoby::Saturn.new(ephem: ephem, instant: instant)
142
144
  horizontal = saturn.observed_by(observer).horizontal
143
145
 
144
146
  horizontal.altitude.str(:dms)
145
- # => "+28° 46′ 39.5994″"
147
+ # => "+28° 46′ 38.3398″"
146
148
  ```
147
149
 
148
150
  ### Azimuth
@@ -152,15 +154,73 @@ eastward.
152
154
 
153
155
  ```rb
154
156
  horizontal.azimuth.str(:dms)
155
- # => "+171° 19′ 50.5798″"
157
+ # => "+171° 19′ 38.2567″"
156
158
  ```
157
159
 
160
+ ## Geodetic
161
+
162
+ Geodetic coordinates describe a position on or above the Earth's surface using
163
+ the [WGS-84] reference ellipsoid, the same system used by GPS. They have three
164
+ attributes:
165
+
166
+ ### Latitude
167
+
168
+ The angle from the equator to the point on the ellipsoid surface, from +90°
169
+ (North Pole) to -90° (South Pole).
170
+
171
+ ### Longitude
172
+
173
+ The angle from the Greenwich meridian, from +180° to -180°, with positive
174
+ angles eastward.
175
+
176
+ ### Elevation
177
+
178
+ The distance above (or below) the WGS-84 ellipsoid surface.
179
+
180
+ ### From ECEF
181
+
182
+ Geodetic coordinates can be derived from Earth-Centered Earth-Fixed (ECEF)
183
+ Cartesian coordinates using `Astronoby::Coordinates::Geodetic.from_ecef`. This
184
+ is particularly useful for satellite tracking, where a propagator produces ECEF
185
+ positions via `Astronoby::Teme#to_ecef`:
186
+
187
+ ```rb
188
+ teme = Astronoby::Teme.new(
189
+ position: Astronoby::Distance.vector_from_meters(
190
+ [5_094_180.16, 6_127_644.66, 6_380_344.53]
191
+ ),
192
+ velocity: Astronoby::Velocity.vector_from_mps(
193
+ [-4746.13, 785.82, 5531.93]
194
+ ),
195
+ instant: instant
196
+ )
197
+
198
+ geodetic = teme.to_ecef.geodetic
199
+
200
+ geodetic.latitude.str(:dms)
201
+ # => "+38° 48′ 3.3038″"
202
+
203
+ geodetic.longitude.str(:dms)
204
+ # => "+97° 27′ 6.7374″"
205
+
206
+ geodetic.elevation.km.round
207
+ # => 3838
208
+ ```
209
+
210
+ The conversion uses Bowring's iterative method, which converges in 2-3
211
+ iterations for typical satellite altitudes.
212
+
213
+ The forward direction (geodetic → ECEF) is handled by
214
+ [`Astronoby::Observer#geocentric_position`][Observer page].
215
+
216
+ [WGS-84]: https://en.wikipedia.org/wiki/World_Geodetic_System
158
217
  [reference frame]: reference_frames.md
159
218
  [Wikipedia]: https://en.wikipedia.org/wiki/Equatorial_coordinate_system
160
219
  [Angle page]: angles.md
161
220
  [topocentric position]: reference_frames.md#topocentric
162
221
 
163
222
  ## See also
223
+
164
224
  - [Reference Frames](reference_frames.md) - for coordinate system details
165
225
  - [Angles](angles.md) - for working with angular measurements
166
226
  - [Observer](observer.md) - for location setup
@@ -63,7 +63,7 @@ vega_with_proper_motion
63
63
  .equatorial
64
64
  .right_ascension
65
65
  .str(:hms)
66
- # => "18h 37m 48.7215s"
66
+ # => "18h 37m 48.706s"
67
67
  ```
68
68
 
69
69
  You can learn more about [ephemerides] and [reference frames].
@@ -96,6 +96,51 @@ event.rising_time
96
96
  # => 2025-10-01 01:31:25 UTC
97
97
  ```
98
98
 
99
+ ## Many points at once
100
+
101
+ Getting the horizontal coordinates of a single object goes through the whole
102
+ chain of reference frames. Doing so for thousands of points at the same instant
103
+ for the same observer, like the stars of a catalogue for a sky chart, repeats
104
+ the same work for each of them: the rotation from the ICRS to the true equator
105
+ and equinox of date, and the sidereal time only depend on the observer and the
106
+ instant.
107
+
108
+ `Astronoby::HorizontalTransformation` computes them once, and then converts
109
+ any equatorial coordinates at J2000 epoch into horizontal coordinates. It is
110
+ about 70 times faster per point than going through
111
+ `DeepSkyObject#at(instant).observed_by(observer).horizontal`.
112
+
113
+ ```rb
114
+ observer = Astronoby::Observer.new(
115
+ latitude: Astronoby::Angle.from_degrees(51.5072),
116
+ longitude: Astronoby::Angle.from_degrees(-0.1276)
117
+ )
118
+ instant = Astronoby::Instant.from_time(Time.utc(2025, 10, 1, 3))
119
+
120
+ sirius = Astronoby::Coordinates::Equatorial.new(
121
+ right_ascension: Astronoby::Angle.from_hms(6, 45, 8.917),
122
+ declination: Astronoby::Angle.from_dms(-16, 42, 58.02)
123
+ )
124
+
125
+ transformation = Astronoby::HorizontalTransformation.new(
126
+ observer: observer,
127
+ instant: instant
128
+ )
129
+ horizontal = transformation.apply(sirius)
130
+
131
+ horizontal.altitude.str(:dms, precision: 0)
132
+ # => "+10° 36′ 27″"
133
+ horizontal.azimuth.str(:dms, precision: 0)
134
+ # => "+134° 56′ 25″"
135
+ ```
136
+
137
+ The result is the same as the one of a `DeepSkyObject` without an ephemeris,
138
+ which means the points are treated as fixed directions: there is no aberration,
139
+ no proper motion and no parallax. Atmospheric refraction is not applied by
140
+ default, and can be requested with `transformation.apply(sirius, refraction:
141
+ true)`. The coordinates must be at J2000 epoch, otherwise an
142
+ `Astronoby::IncompatibleArgumentsError` is raised.
143
+
99
144
  [ephemerides]: ephem.md
100
145
  [reference frames]: reference_frames.md
101
146
  [SIMBAD Astronomical Database]: https://simbad.u-strasbg.fr/simbad/
data/docs/ephem.md CHANGED
@@ -17,8 +17,9 @@ providing the filename.
17
17
  ### Manually
18
18
 
19
19
  Ephemerides are available for download from:
20
- * the JPL public FTP interface: https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/
21
- * the INPOP releases page of the IMCCE: https://www.imcce.fr/inpop
20
+
21
+ - the JPL public FTP interface: https://ssd.jpl.nasa.gov/ftp/eph/planets/bsp/
22
+ - the INPOP releases page of the IMCCE: https://www.imcce.fr/inpop
22
23
 
23
24
  You can download any `.bsp` file. For the moment, Astronoby only supports the
24
25
  planets of the Solar System, and the Sun and the Moon, so you need to
@@ -48,6 +49,7 @@ ephem = Astronoby::Ephem.load("tmp/de421.bsp")
48
49
 
49
50
  JPL produces many different kernels over the years, with different accuracy and
50
51
  ranges of supported years. Here are some that we recommend to begin with:
52
+
51
53
  - `de421.bsp`: from 1900 to 2050, 17 MB
52
54
  - `de440s.bsp`: from 1849 to 2150, 32 MB
53
55
  - `inpop19a.bsp`: from 1900 to 2100, 22 MB
@@ -79,6 +81,7 @@ the target `399`.
79
81
  [IMCCE]: https://www.imcce.fr
80
82
 
81
83
  ## See also
84
+
82
85
  - [Solar System Bodies](solar_system_bodies.md) - for using ephemeris data
83
86
  - [Reference Frames](reference_frames.md) - for coordinate calculations
84
87
  - [Observer](observer.md) - for location-based calculations
@@ -15,16 +15,17 @@ Astronoby::EquinoxSolstice.march_equinox(2025, ephem)
15
15
  # => 2025-03-20 09:01:29 UTC
16
16
 
17
17
  Astronoby::EquinoxSolstice.june_solstice(2025, ephem)
18
- # => 2025-06-21 02:42:19 UTC
18
+ # => 2025-06-21 02:42:16 UTC
19
19
 
20
20
  Astronoby::EquinoxSolstice.september_equinox(2025, ephem)
21
- # => 2025-09-22 18:19:22 UTC
21
+ # => 2025-09-22 18:19:21 UTC
22
22
 
23
23
  Astronoby::EquinoxSolstice.december_solstice(2025, ephem)
24
- # => 2025-12-21 15:03:03 UTC
24
+ # => 2025-12-21 15:03:05 UTC
25
25
  ```
26
26
 
27
27
  ## See also
28
+
28
29
  - [Twilight Times](twilight_times.md) - for seasonal sun events
29
30
  - [Moon Phases](moon_phases.md) - for lunar events
30
31
  - [Ephemerides](ephem.md) - for data sources