astronoby 0.10.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +134 -0
  3. data/README.md +13 -0
  4. data/UPGRADING.md +186 -0
  5. data/docs/README.md +32 -8
  6. data/docs/altitude_crossings.md +92 -0
  7. data/docs/configuration.md +8 -0
  8. data/docs/deep_sky_bodies.md +45 -0
  9. data/docs/glossary.md +2 -0
  10. data/docs/instant.md +14 -7
  11. data/docs/lunar_eclipses.md +128 -13
  12. data/docs/twilight_times.md +26 -11
  13. data/lib/astronoby/bodies/deep_sky_object_position.rb +6 -24
  14. data/lib/astronoby/cache.rb +13 -0
  15. data/lib/astronoby/constants.rb +1 -2
  16. data/lib/astronoby/constellations/data.rb +1 -1
  17. data/lib/astronoby/coordinates/equatorial.rb +15 -5
  18. data/lib/astronoby/delta_t.rb +44 -0
  19. data/lib/astronoby/events/altitude_crossing.rb +29 -0
  20. data/lib/astronoby/events/altitude_crossing_calculator.rb +511 -0
  21. data/lib/astronoby/events/altitude_extreme.rb +31 -0
  22. data/lib/astronoby/events/lunar_eclipse.rb +49 -37
  23. data/lib/astronoby/events/lunar_eclipse_calculator.rb +143 -31
  24. data/lib/astronoby/events/lunar_eclipse_geometry.rb +115 -0
  25. data/lib/astronoby/events/lunar_eclipse_phase.rb +45 -0
  26. data/lib/astronoby/events/lunar_eclipse_visibility.rb +296 -0
  27. data/lib/astronoby/events/rise_transit_set_calculator.rb +9 -13
  28. data/lib/astronoby/events/twilight_calculator.rb +37 -147
  29. data/lib/astronoby/horizon.rb +40 -0
  30. data/lib/astronoby/horizontal_transformation.rb +112 -0
  31. data/lib/astronoby/instant.rb +50 -13
  32. data/lib/astronoby/precession.rb +1 -1
  33. data/lib/astronoby/reference_frame.rb +8 -3
  34. data/lib/astronoby/reference_frames/apparent.rb +20 -8
  35. data/lib/astronoby/reference_frames/mean_of_date.rb +6 -0
  36. data/lib/astronoby/reference_frames/teme.rb +6 -0
  37. data/lib/astronoby/reference_frames/topocentric.rb +6 -0
  38. data/lib/astronoby/root_finder.rb +16 -9
  39. data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +10 -8
  40. data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +11 -9
  41. data/lib/astronoby/util/time.rb +0 -22
  42. data/lib/astronoby/version.rb +1 -1
  43. data/lib/astronoby.rb +9 -1
  44. metadata +40 -5
  45. data/lib/astronoby/events/eclipse_phase.rb +0 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3e781560f80c12781becc942c58f026386dbc252bff0830ab154e1217dba319f
4
- data.tar.gz: '0939683834940fe9bef423fe6e59b5a8ce5a1c53921015ee612c0f258c0fd224'
3
+ metadata.gz: 1093c9a0d077cfa9c2a9fcf60e2af2a21ec4c1f74c268ef22dca8e93b5badadb
4
+ data.tar.gz: e12ccfad49e7a63821cde3b382e8584efdff3c0c06a42ffddf60119ad213b2f5
5
5
  SHA512:
6
- metadata.gz: 731fe3a326ddeca238f9479647e61e0554bbef2e10a35e468079240a58e8482a3e21b316fc61d5bcfb6962de353002e882c1e104f3e5c1626f6a02177a55a9a1
7
- data.tar.gz: 035fb37802b80e6be88c216d0b117e3804bcd55c75871c4de4d6554e55fe8634f39e7dfe73e794bef4d00d35e59ccdbab65851bf34525a05f3ef290c4a5d2373
6
+ metadata.gz: cc73661c433669d3b9efcd816088513a031d7c8c609844988e0ecabed36959e9a361d15d8cbd87905f83bfcb3685640dbad1a7862d624a6895b0b78fe0aa54f7
7
+ data.tar.gz: 7c544cd27042ec8436d2c11a9f27c6269e039256dadb1ba80a62dac6b3140669420c336df950077aa6a54786158f5c2f3b91744b3cacf67de2003db686ffca37
data/CHANGELOG.md CHANGED
@@ -1,5 +1,139 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 - 2026-10-31
4
+
5
+ _If you are upgrading: please see [UPGRADING.md]._
6
+
7
+ ### Bug fixes
8
+
9
+ * Apply nutation after precession for a `DeepSkyObject` without an ephemeris.
10
+ The two rotations were composed in the wrong order, so the apparent position
11
+ was off by up to about 0.1 arcsecond depending on the date, and the velocity
12
+ had the same error. The rotation now matches ERFA and Skyfield, and is the
13
+ one used when an ephemeris is given. Without an ephemeris there is still no
14
+ aberration correction ([#316])
15
+ * Let the rise, transit and set refinement move an estimate back in time. The
16
+ correction was clamped to be positive, so an estimate that overshot its event
17
+ could never be brought back, and settings and transits came out up to a few
18
+ minutes late depending on the window asked for ([#301])
19
+ * Refer equatorial coordinates to the epoch of the frame they come from
20
+ ([#302])
21
+ * Return real Barycentric Dynamical Time from `Instant#tdb`, which returned
22
+ Terrestrial Time unchanged. The two scales differ by up to about 1.7
23
+ milliseconds ([#304])
24
+ * Keep ΔT at its last measured value past the end of the IERS series. `iers`
25
+ 0.3.0 stopped attaching an `available_range` to the `OutOfRangeError` it
26
+ raises for an uncovered date, and the fallback read that absence as "no data
27
+ at all" and answered 0. Every date after the bundled series, currently
28
+ 2027-09-04, was off by about 69 seconds ([#304])
29
+ * Read the constellation data files as UTF-8, so that `Constellations::Finder`
30
+ and `Constellations::Repository` work when the default external encoding is
31
+ not UTF-8, as under `LANG=C`
32
+
33
+ ### Features
34
+
35
+ * Add `HorizontalTransformation`, which converts equatorial coordinates at
36
+ J2000 epoch into horizontal coordinates for one observer and one instant. The
37
+ rotation and the sidereal time are computed once, which makes converting many
38
+ points, like the stars of a catalogue, about 70 times faster than going
39
+ through `DeepSkyObject#at(instant).observed_by(observer).horizontal`. Points
40
+ are treated as fixed directions, as for a `DeepSkyObject` without an
41
+ ephemeris. `refraction:` is an option ([#317])
42
+ * Add `AltitudeCrossingCalculator`, which finds when a body reaches any
43
+ altitude, with unrounded instants, the crossing direction, a grazing flag and
44
+ the culminations. `refraction:` and `reference:` (`:center`, `:upper_limb`,
45
+ `:lower_limb`) are options ([#311])
46
+ * Expose lunar eclipse shadow geometry at greatest eclipse and at every contact
47
+ ([#300])
48
+ * Add lunar eclipse local circumstances with `LunarEclipse#visibility_from`
49
+ ([#302])
50
+ * Expose the Moon's apparent place at every lunar eclipse contact ([#302])
51
+
52
+ ### Improvements
53
+
54
+ * Cache Greenwich mean and apparent sidereal times when the cache is enabled.
55
+ The cache is still disabled by default, which is now stated in the README
56
+ ([#320])
57
+ * Compute the TAI and TDB time scales with the horologium gem, which owns the
58
+ scales that convert by definition or by model. UT1 stays in Astronoby
59
+ ([#304])
60
+ * Read `Instant#tai` and `#tdb` as a `Rational`, which a `Float` Julian Date is
61
+ too coarse to hold ([#304])
62
+ * Evaluate `Precession` in TT, as SOFA and ERFA do ([#304])
63
+ * Raise the minimum `iers` version from 0.1 to 0.3.1, which `horologium`
64
+ requires ([#304])
65
+ * Share the rise and set horizon convention through `Astronoby::Horizon`
66
+ ([#311])
67
+ * Derive a lunar eclipse's magnitudes, axis distance and gamma from its
68
+ geometry ([#300], [#302])
69
+ * Resolve the lunar eclipse shadow axis offset with vectors ([#302])
70
+ * Reduce the Moon to the observer by vectors for lunar eclipse visibility
71
+ ([#302])
72
+ * Answer lunar eclipse phase coverage in the words the whole eclipse uses
73
+ ([#302])
74
+ * Pin CI runner to ubuntu-24.04 ([#312])
75
+ * Fix CI for the standard and simplecov bumps ([#319])
76
+ * Bump rubyzip from 3.4.0 to 3.7.0 by @dependabot ([#285], [#299], [#305],
77
+ [#308])
78
+ * Bump standard from 1.55.0 to 1.56.0 by @dependabot ([#288])
79
+ * Bump simplecov from 0.22 to 1.0.3 by @dependabot ([#287], [#289], [#291])
80
+ * Bump iers from 0.1.0 to 0.2.0 by @dependabot ([#284], [#294])
81
+ * Update json to 2.21.2 ([#296])
82
+
83
+ ### Backward-incompatible changes
84
+
85
+ * `TwilightCalculator` is based on `AltitudeCrossingCalculator`. Times change
86
+ slightly: up to about 30 seconds at the standard angles, and up to about 100
87
+ seconds with `time_for_zenith_angle` and custom angles. The new values match
88
+ IMCCE, Horizons and Skyfield. Twilight that exists is returned when the Sun
89
+ doesn't rise or set, as in a polar night, instead of `nil`. Results are still
90
+ rounded to the second. `#event_on` is about 1.8 times slower than before,
91
+ because it solves for the real crossing instead of approximating the Sun's
92
+ position from a single evaluation ([#311])
93
+ * Equatorial coordinates carry the epoch of the frame they come from ([#302])
94
+ * `Instant#tdb` returns real TDB, and `#tai` and `#tdb` return a `Rational`
95
+ ([#304])
96
+ * Remove `Constants::TAI_TT_OFFSET` ([#304])
97
+ * `Util::Time.terrestrial_universal_time_delta` is replaced by `DeltaT.at`
98
+ ([#295])
99
+ * `EclipsePhase` is now `LunarEclipsePhase`, `LunarEclipse#greatest_eclipse` is
100
+ now `#greatest_eclipse_instant`, `LunarEclipse.new` no longer accepts the
101
+ magnitudes, gamma or shadow axis distance, and `LunarEclipsePhase#duration` is
102
+ no longer rounded ([#300], [#302])
103
+ * Remove `LunarEclipseVisibility#altitude_at` ([#302])
104
+ * Lunar eclipse contacts are reduced with the IAU eclipse constant k. Contact
105
+ times may shift by a second or two, and magnitudes by up to about 0.001
106
+ ([#302])
107
+ * Bisect to a millisecond rather than to a second when locating an event in
108
+ time. Conjunction and opposition times may move by a fraction of a second, and
109
+ searches are about 20% slower ([#302])
110
+
111
+ **Full Changelog**: https://github.com/rhannequin/astronoby/compare/v0.10.0...v0.11.0
112
+
113
+ [#284]: https://github.com/rhannequin/astronoby/pull/284
114
+ [#285]: https://github.com/rhannequin/astronoby/pull/285
115
+ [#287]: https://github.com/rhannequin/astronoby/pull/287
116
+ [#288]: https://github.com/rhannequin/astronoby/pull/288
117
+ [#289]: https://github.com/rhannequin/astronoby/pull/289
118
+ [#291]: https://github.com/rhannequin/astronoby/pull/291
119
+ [#294]: https://github.com/rhannequin/astronoby/pull/294
120
+ [#295]: https://github.com/rhannequin/astronoby/pull/295
121
+ [#296]: https://github.com/rhannequin/astronoby/pull/296
122
+ [#299]: https://github.com/rhannequin/astronoby/pull/299
123
+ [#300]: https://github.com/rhannequin/astronoby/pull/300
124
+ [#301]: https://github.com/rhannequin/astronoby/pull/301
125
+ [#302]: https://github.com/rhannequin/astronoby/pull/302
126
+ [#304]: https://github.com/rhannequin/astronoby/pull/304
127
+ [#305]: https://github.com/rhannequin/astronoby/pull/305
128
+ [#308]: https://github.com/rhannequin/astronoby/pull/308
129
+ [#311]: https://github.com/rhannequin/astronoby/pull/311
130
+ [#312]: https://github.com/rhannequin/astronoby/pull/312
131
+ [#316]: https://github.com/rhannequin/astronoby/pull/316
132
+ [#317]: https://github.com/rhannequin/astronoby/pull/317
133
+ [#319]: https://github.com/rhannequin/astronoby/pull/319
134
+ [#320]: https://github.com/rhannequin/astronoby/pull/320
135
+ [UPGRADING.md]: https://github.com/rhannequin/astronoby/blob/main/UPGRADING.md
136
+
3
137
  ## 0.10.0 - 2026-06-26
4
138
 
5
139
  _If you are upgrading: please see [UPGRADING.md]._
data/README.md CHANGED
@@ -73,6 +73,19 @@ automated guiding of amateur telescopes and some professional use-cases.
73
73
  The sources used for comparison are: [IMCCE], [JPL Horizons], [Stellarium],
74
74
  and the [Skyfield] library.
75
75
 
76
+ ## Performance
77
+
78
+ Astronoby has an internal cache for intermediate results like nutation,
79
+ precession and sidereal time. It is disabled by default. It helps when the same
80
+ computation is repeated at one instant, for example for many bodies or many
81
+ points seen by one observer, and can make it about 2.5 times faster:
82
+
83
+ ```rb
84
+ Astronoby.configuration.cache_enabled = true
85
+ ```
86
+
87
+ See the [configuration documentation](docs/configuration.md) for the details.
88
+
76
89
  ## Development
77
90
 
78
91
  After checking out the repo, run `bin/setup` to install dependencies. Then, run
data/UPGRADING.md CHANGED
@@ -7,6 +7,192 @@ changes to it as long as a major version has not been released.
7
7
  If you are already using Astronoby and wish to follow the changes to its
8
8
  public API, please read the upgrading notes for each release.
9
9
 
10
+ ## Upgrading from 0.10.0 to 0.11.0
11
+
12
+ ### Lunar eclipse contacts and magnitudes have changed slightly
13
+
14
+ Lunar eclipse contacts are now reduced with the IAU eclipse constant
15
+ k = 0.2725076 Earth radii, putting the Moon's radius at 1738.09 km, instead of
16
+ its physical equatorial radius of 1737.4 km. This is what IMCCE and NASA reduce
17
+ eclipses with. The atmospheric enlargement of Earth's shadow has been
18
+ recalibrated alongside it, from 1/99 of Earth's radius to a 64 km shell.
19
+
20
+ The two constants have to move together: either one alone makes agreement with
21
+ IMCCE worse, because the old 1/99 enlargement was partly compensating for the
22
+ smaller lunar radius. Together they cut the contact-time error against IMCCE by
23
+ more than half, and the worst case over the 2026 to 2048 eclipses from 8.9
24
+ seconds to 2.2 seconds.
25
+
26
+ Contact times may shift by a second or two, and the magnitudes by up to about
27
+ 0.001. Greatest eclipse, `#gamma` and `#shadow_axis_distance` are unaffected,
28
+ since they depend on neither constant.
29
+
30
+ ### `EclipsePhase` is now `LunarEclipsePhase`
31
+
32
+ `Astronoby::EclipsePhase` has been renamed to `Astronoby::LunarEclipsePhase`.
33
+ Its geometry is a `LunarEclipseGeometry`, so the class was never general to
34
+ every kind of eclipse, and the old name reserved a place that solar eclipses
35
+ will need.
36
+
37
+ ### A lunar eclipse and its phases are built from fewer values
38
+
39
+ `Astronoby::LunarEclipsePhase.new` requires `starting_geometry:` and
40
+ `ending_geometry:`, and `Astronoby::LunarEclipse.new` requires `geometry:`,
41
+ each an `Astronoby::LunarEclipseGeometry`.
42
+
43
+ `LunarEclipse.new` no longer accepts `umbral_magnitude:`,
44
+ `penumbral_magnitude:`, `gamma:` or `shadow_axis_distance:`. All four are now
45
+ read off the geometry at greatest eclipse, so an eclipse can no longer be
46
+ built holding values that contradict its own geometry. The readers are
47
+ unchanged.
48
+
49
+ Both objects are built by `Astronoby::LunarEclipseCalculator`, so this only
50
+ affects code constructing them directly.
51
+
52
+ ### `LunarEclipse#greatest_eclipse` is now `#greatest_eclipse_instant`
53
+
54
+ The method returns an `Astronoby::Instant`, not an eclipse. `#instant` remains
55
+ available and is the same value.
56
+
57
+ ### `LunarEclipsePhase#duration` is no longer rounded
58
+
59
+ The duration was rounded to whole seconds. It is now measured on the
60
+ terrestrial time scale, which is continuous, so it is unaffected by a leap
61
+ second falling inside the phase, and it keeps the fraction of a second.
62
+ `#duration.seconds` may now return a Float with a fractional part.
63
+
64
+ ### Equatorial coordinates carry the epoch of the frame they came from
65
+
66
+ `#equatorial` reported the J2000 epoch on every reference frame, including
67
+ frames referred to the equator and equinox of date. Precessing a mean of date,
68
+ apparent, topocentric or TEME place was therefore a no operation rather than
69
+ removing the precession accumulated since J2000, and `GeocentricParallax`
70
+ carried the wrong epoch into anything derived from one.
71
+
72
+ `MeanOfDate`, `Apparent`, `Topocentric` and `Teme` now report their own
73
+ instant as the epoch, through a new `#epoch` reader on `ReferenceFrame`.
74
+ `Geometric` and `Astrometric` are unchanged, since both are built on the ICRS
75
+ and were already right.
76
+
77
+ Code passing an of date coordinate to
78
+ `Precession.for_equatorial_coordinates` will now get a precessed result where
79
+ it previously got the coordinate back nearly unchanged.
80
+
81
+ ### Twilight times are computed from altitude crossings
82
+
83
+ `TwilightCalculator` is now built on `AltitudeCrossingCalculator`, which finds
84
+ the instant the Sun's centre reaches each altitude directly. The previous
85
+ approach was an approximation that could be off by tens of seconds.
86
+
87
+ Times change slightly: up to about 30 seconds at the standard angles, and up to
88
+ about 100 seconds with `time_for_zenith_angle` and custom angles. The new
89
+ values agree with IMCCE, JPL Horizons and Skyfield. Results are still rounded
90
+ to the second.
91
+
92
+ When the Sun doesn't rise or set, as in a polar night, the twilight that does
93
+ happen is now returned instead of `nil`. A twilight that never happens, such as
94
+ astronomical twilight at high latitude in midsummer, is still `nil`.
95
+
96
+ `TwilightCalculator#event_on` is about 1.8 times slower than before, from around
97
+ 4 to around 7.5 milliseconds for one day. The previous version approximated the
98
+ Sun's position from a single evaluation, which could be off by up to 30
99
+ seconds. This one solves for the real crossing, evaluating the Sun's position
100
+ about 20 times, and agrees with IMCCE, JPL Horizons and Skyfield to the second.
101
+
102
+ ### Rise, transit and set times are no longer late
103
+
104
+ The refinement of a rise, transit or set time could only move its estimate
105
+ forward in time. An estimate that overshot its event was never brought back, so
106
+ settings and transits came out up to a few minutes late depending on the window
107
+ asked for. Moon transits now agree with Skyfield to the second. Expect Moon
108
+ times to move by up to a few minutes, and Sun and planet times by a few
109
+ seconds.
110
+
111
+ ### Event times are located more precisely
112
+
113
+ Bisection accepted a root once its bracket fell under 0.864 seconds, so a
114
+ reported time could sit up to 0.43 seconds from the root of the model it came
115
+ from. It now bisects to under a millisecond. Conjunction and opposition times
116
+ may move by a fraction of a second, and a time that sat near a whole second
117
+ may round to the next one.
118
+
119
+ A sample landing exactly on a root is also no longer missed, and bisection
120
+ stops if a bracket reaches the resolution of a Float rather than looping.
121
+
122
+ ### New `horologium` dependency, and `iers` raised to 0.3.1
123
+
124
+ The TAI and TDB time scales are now computed by the
125
+ [`horologium`](https://github.com/rhannequin/horologium) gem, which owns the
126
+ scales that convert by definition or by model. UT1 stays in Astronoby: it
127
+ follows the actual rotation of the Earth rather than a definition, and comes
128
+ from `Astronoby::DeltaT`. Nothing else about time changes, and `Instant#tt`,
129
+ `#to_time`, `#to_date`, `#to_datetime`, `#delta_t`, `#gmst`, `#gast`, `#lmst`
130
+ and `#last` are untouched.
131
+
132
+ `horologium` requires `iers` 0.3.1, so the minimum `iers` version is raised
133
+ from 0.1 to 0.3.1. One `iers` API Astronoby used did change on the way: `iers`
134
+ 0.3.0 stopped attaching an `available_range` to the `OutOfRangeError` that
135
+ `DeltaT.at` raises for a date it cannot answer. Astronoby reads the end of the
136
+ published series directly now, so ΔT past that end still comes back as the last
137
+ measured value rather than 0.
138
+
139
+ Astronoby pins the precision it asks `horologium` for, so an application that
140
+ calls `Horologium.configure` for its own purposes cannot change what Astronoby
141
+ computes.
142
+
143
+ ### `Instant#tdb` now returns Barycentric Dynamical Time
144
+
145
+ `tdb` previously returned Terrestrial Time unchanged, documented in the source
146
+ as an approximation. It now returns real TDB, from the Fairhead and Bretagnon
147
+ series that ERFA uses. The two scales differ by up to about 1.7 milliseconds.
148
+
149
+ ```rb
150
+ instant = Astronoby::Instant.from_terrestrial_time(2460796)
151
+
152
+ # Before
153
+ instant.tdb # => 2460796
154
+
155
+ # After
156
+ instant.tdb.to_f # => 2460796.000000017
157
+ ```
158
+
159
+ `Precession` is now evaluated in TT, as SOFA and ERFA do. It read `tdb` before,
160
+ which was TT under another name, so precession results are unchanged.
161
+
162
+ ### `Instant#tai` and `#tdb` return a `Rational`
163
+
164
+ Both are read as a `Rational` rather than a `Float`. A Julian Date is around
165
+ 2.46 million, so a single `Float` rounds to about 47 microseconds of the day,
166
+ which is more than the whole TDB - TT correction. Call `to_f` where a `Float`
167
+ is what you want.
168
+
169
+ `tai` is now exactly 32.184 seconds behind TT. It was within a few femtoseconds
170
+ of that for an instant built from an `Integer` or a `Rational`, the offset
171
+ having been built from a `Float` literal, and up to 47 microseconds off for one
172
+ built from a `Float`, where the result came back as a `Float` too. So the type
173
+ changes in that last case, and `tai.to_f` can land one `Float` step from where
174
+ it used to, closer to the true value.
175
+
176
+ ### Drop of `Constants::TAI_TT_OFFSET`
177
+
178
+ `Astronoby::Constants::TAI_TT_OFFSET` has been removed. The offset between TAI
179
+ and TT is `horologium`'s to hold now, and Astronoby no longer names it.
180
+
181
+ ### `Util::Time.terrestrial_universal_time_delta` replaced by `DeltaT.at`
182
+
183
+ Delta T (TT - UT1) now has its own namespace instead of living in
184
+ `Astronoby::Util::Time`. The returned value is unchanged.
185
+
186
+ ```rb
187
+ # Before
188
+ Astronoby::Util::Time.terrestrial_universal_time_delta(time)
189
+
190
+ # After
191
+ Astronoby::DeltaT.at(time)
192
+ ```
193
+
194
+ `Astronoby::Util::Time` keeps `decimal_hour_to_time`.
195
+
10
196
  ## Upgrading from 0.9.0 to 0.10.0
11
197
 
12
198
  ### Minimum Ruby version bumped to 3.2.0
data/docs/README.md CHANGED
@@ -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:07 UTC
111
+ # => 2025-05-01 02:17:02 UTC
112
112
 
113
113
  event.morning_nautical_twilight_time
114
- # => 2025-05-01 03:09:44 UTC
114
+ # => 2025-05-01 03:09:39 UTC
115
115
 
116
116
  event.morning_civil_twilight_time
117
- # => 2025-05-01 03:54:38 UTC
117
+ # => 2025-05-01 03:54:36 UTC
118
118
 
119
119
  event.evening_civil_twilight_time
120
- # => 2025-05-01 19:41:51 UTC
120
+ # => 2025-05-01 19:41:58 UTC
121
121
 
122
122
  event.evening_nautical_twilight_time
123
- # => 2025-05-01 20:26:56 UTC
123
+ # => 2025-05-01 20:27:15 UTC
124
124
 
125
125
  event.evening_astronomical_twilight_time
126
- # => 2025-05-01 21:19:57 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
@@ -191,7 +212,7 @@ eclipses = Astronoby::Moon.eclipse_events(
191
212
  )
192
213
 
193
214
  eclipses.map { |eclipse| [eclipse.instant.to_time, eclipse.kind] }
194
- # => [[2025-03-14 06:58:47 UTC, :total], [2025-09-07 18:11:49 UTC, :total]]
215
+ # => [[2025-03-14 06:58:46 UTC, :total], [2025-09-07 18:11:49 UTC, :total]]
195
216
  ```
196
217
 
197
218
  You can learn more about umbral and penumbral magnitudes, phases and contact
@@ -263,7 +284,9 @@ vega_position.apparent.equatorial.right_ascension.str(:hms)
263
284
  # => "18h 37m 48.2804s"
264
285
  ```
265
286
 
266
- 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].
267
290
 
268
291
  ## See also
269
292
 
@@ -281,6 +304,7 @@ You can learn more about deep-sky objects on the [Deep-sky Bodies page].
281
304
  [Coordinates page]: coordinates.md
282
305
  [Rise, transit and setting times page]: rise_transit_set_times.md
283
306
  [Twilight times page]: twilight_times.md
307
+ [Altitude crossings page]: altitude_crossings.md
284
308
  [Moon phases page]: moon_phases.md
285
309
  [Lunar observation page]: lunar_observation.md
286
310
  [Lunar eclipses page]: lunar_eclipses.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)
@@ -26,6 +26,14 @@ with default precision values:
26
26
  - [Moon phases]
27
27
  - Nutation
28
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.
29
37
 
30
38
  ## Cache precision
31
39
 
@@ -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/glossary.md CHANGED
@@ -238,6 +238,8 @@ The primary time standard used worldwide. All astronomical calculations in Astro
238
238
 
239
239
  - **Julian Day**: Continuous day count since 4713 BCE
240
240
  - **Terrestrial Time (TT)**: Astronomical time scale used for calculations
241
+ - **International Atomic Time (TAI)**: The atomic time scale TT is defined against, 32.184 seconds behind it
242
+ - **Barycentric Dynamical Time (TDB)**: Time at the Solar System barycentre, within about 1.7 milliseconds of TT
241
243
  - **UTC**: Coordinated Universal Time, the standard for civil time
242
244
 
243
245
  ## See also
data/docs/instant.md CHANGED
@@ -25,7 +25,10 @@ International Astronomical Union, as a [Julian Date].
25
25
  From this instant in <abbr title="Terrestrial Time">TT</abbr>, other time
26
26
  standards can be expressed, such as the
27
27
  <abbr title="International Atomic Time">TAI</abbr> or the
28
- <abbr title="Barycentric Dynamic Time">TDB</abbr>.
28
+ <abbr title="Barycentric Dynamical Time">TDB</abbr>. Those two are computed by
29
+ the [horologium] gem, which owns the time scales that convert by definition or
30
+ by model. UT1 is not one of them: it follows the actual rotation of the Earth,
31
+ so it stays in Astronoby, backed by [IERS data](iers.md).
29
32
 
30
33
  ## Initialization
31
34
 
@@ -93,7 +96,7 @@ instant.gmst
93
96
  instant = Astronoby::Instant.from_terrestrial_time(2460796)
94
97
 
95
98
  instant.tai.to_f
96
- # => 2460795.9996275003
99
+ # => 2460795.9996275
97
100
  ```
98
101
 
99
102
  - UTC offset (difference with UTC in days)
@@ -105,17 +108,20 @@ instant.utc_offset.to_f
105
108
  # => 0.0008003974106626159
106
109
  ```
107
110
 
108
- - Barycentric Dynamic Time
111
+ - Barycentric Dynamical Time
109
112
 
110
113
  ```rb
111
- # This is not handled for now, and returns TT
112
-
113
114
  instant = Astronoby::Instant.from_terrestrial_time(2460796)
114
115
 
115
- instant.tdb
116
- # => 2460796
116
+ instant.tdb.to_f
117
+ # => 2460796.000000017
117
118
  ```
118
119
 
120
+ `tai` and `tdb` are read as a `Rational`. A Julian Date is around 2.46
121
+ million, so a single `Float` rounds to about 47 microseconds of the day, which
122
+ is more than the whole TDB - TT correction. Call `to_f` when a `Float` is what
123
+ you want.
124
+
119
125
  ## Value Equality
120
126
 
121
127
  As a value object, it is possible to compare different instants.
@@ -131,6 +137,7 @@ instant1 < instant2
131
137
  # => true
132
138
  ```
133
139
 
140
+ [horologium]: https://github.com/rhannequin/horologium
134
141
  [Gregorian calendar]: https://en.wikipedia.org/wiki/Gregorian_calendar
135
142
  [Terrestrial Time]: https://en.wikipedia.org/wiki/Terrestrial_Time
136
143
  [Julian Date]: https://en.wikipedia.org/wiki/Julian_day