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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +134 -0
- data/README.md +13 -0
- data/UPGRADING.md +186 -0
- data/docs/README.md +32 -8
- data/docs/altitude_crossings.md +92 -0
- data/docs/configuration.md +8 -0
- data/docs/deep_sky_bodies.md +45 -0
- data/docs/glossary.md +2 -0
- data/docs/instant.md +14 -7
- data/docs/lunar_eclipses.md +128 -13
- data/docs/twilight_times.md +26 -11
- data/lib/astronoby/bodies/deep_sky_object_position.rb +6 -24
- data/lib/astronoby/cache.rb +13 -0
- data/lib/astronoby/constants.rb +1 -2
- data/lib/astronoby/constellations/data.rb +1 -1
- data/lib/astronoby/coordinates/equatorial.rb +15 -5
- data/lib/astronoby/delta_t.rb +44 -0
- data/lib/astronoby/events/altitude_crossing.rb +29 -0
- data/lib/astronoby/events/altitude_crossing_calculator.rb +511 -0
- data/lib/astronoby/events/altitude_extreme.rb +31 -0
- data/lib/astronoby/events/lunar_eclipse.rb +49 -37
- data/lib/astronoby/events/lunar_eclipse_calculator.rb +143 -31
- data/lib/astronoby/events/lunar_eclipse_geometry.rb +115 -0
- data/lib/astronoby/events/lunar_eclipse_phase.rb +45 -0
- data/lib/astronoby/events/lunar_eclipse_visibility.rb +296 -0
- data/lib/astronoby/events/rise_transit_set_calculator.rb +9 -13
- data/lib/astronoby/events/twilight_calculator.rb +37 -147
- data/lib/astronoby/horizon.rb +40 -0
- data/lib/astronoby/horizontal_transformation.rb +112 -0
- data/lib/astronoby/instant.rb +50 -13
- data/lib/astronoby/precession.rb +1 -1
- data/lib/astronoby/reference_frame.rb +8 -3
- data/lib/astronoby/reference_frames/apparent.rb +20 -8
- data/lib/astronoby/reference_frames/mean_of_date.rb +6 -0
- data/lib/astronoby/reference_frames/teme.rb +6 -0
- data/lib/astronoby/reference_frames/topocentric.rb +6 -0
- data/lib/astronoby/root_finder.rb +16 -9
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +10 -8
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +11 -9
- data/lib/astronoby/util/time.rb +0 -22
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +9 -1
- metadata +40 -5
- data/lib/astronoby/events/eclipse_phase.rb +0 -27
data/docs/lunar_eclipses.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Lunar Eclipses
|
|
2
2
|
|
|
3
|
-
Astronoby computes lunar eclipses: the passages of the Moon through Earth's shadow. A lunar eclipse is a geocentric event, the same for every observer who can see the Moon, so no observer is
|
|
3
|
+
Astronoby computes lunar eclipses: the passages of the Moon through Earth's shadow. A lunar eclipse is a geocentric event, the same for every observer who can see the Moon, so finding one takes no observer. What one place sees of it depends only on whether the Moon is above its horizon, which is covered in [Local circumstances](#local-circumstances) below. The geometry is built from the apparent geocentric positions of the Sun and Moon, which matches the standard reduction used by [NASA's Five Millennium Canon of Lunar Eclipses] and by IMCCE.
|
|
4
4
|
|
|
5
5
|
There are three kinds of lunar eclipses. A penumbral eclipse is when the Moon only enters Earth's penumbra, the outer, partial shadow. A partial eclipse is when part of the Moon enters the umbra, the inner, total shadow. A total eclipse is when the whole Moon enters the umbra.
|
|
6
6
|
|
|
@@ -18,20 +18,20 @@ eclipses = Astronoby::Moon.eclipse_events(
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
eclipses.map { |eclipse| [eclipse.instant.to_time, eclipse.kind] }
|
|
21
|
-
# => [[2025-03-14 06:58:
|
|
21
|
+
# => [[2025-03-14 06:58:46 UTC, :total], [2025-09-07 18:11:49 UTC, :total]]
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## An eclipse
|
|
25
25
|
|
|
26
|
-
Each eclipse is an `Astronoby::LunarEclipse`. Its `#instant` (also available as `#
|
|
26
|
+
Each eclipse is an `Astronoby::LunarEclipse`. Its `#instant` (also available as `#greatest_eclipse_instant`) is the moment of greatest eclipse, when the Moon's centre is least distant from the axis of Earth's shadow.
|
|
27
27
|
|
|
28
28
|
```rb
|
|
29
29
|
eclipse = eclipses.first
|
|
30
30
|
|
|
31
31
|
eclipse.kind # => :total
|
|
32
32
|
eclipse.total? # => true
|
|
33
|
-
eclipse.instant.to_time # => 2025-03-14 06:58:
|
|
34
|
-
eclipse.
|
|
33
|
+
eclipse.instant.to_time # => 2025-03-14 06:58:46 UTC
|
|
34
|
+
eclipse.greatest_eclipse_instant # => same as #instant
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
The umbral and penumbral magnitudes are the fractions of the Moon's diameter immersed in the umbra and the penumbra at greatest eclipse. The umbral magnitude is negative for a penumbral eclipse (the Moon misses the umbra), between 0 and 1 for a partial eclipse, and 1 or more for a total eclipse.
|
|
@@ -41,7 +41,7 @@ eclipse.umbral_magnitude # => 1.179
|
|
|
41
41
|
eclipse.penumbral_magnitude # => 2.26
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
`#gamma` is the least distance of the Moon's centre from the axis of Earth's shadow at greatest eclipse, in Earth radii, positive when the Moon passes north of the axis and negative when it passes south. It is the standard, dimensionless eclipse parameter used by IMCCE and NASA. The same quantity is also available as
|
|
44
|
+
`#gamma` is the least distance of the Moon's centre from the axis of Earth's shadow at greatest eclipse, in Earth radii, positive when the Moon passes north of the axis and negative when it passes south. It is the standard, dimensionless eclipse parameter used by IMCCE and NASA. The same quantity is also available as an `Astronoby::Distance` through `#shadow_axis_distance`, and the side of the axis it takes its sign from is on the geometry as `#north_of_axis?`.
|
|
45
45
|
|
|
46
46
|
```rb
|
|
47
47
|
eclipse.gamma # => 0.348
|
|
@@ -50,21 +50,53 @@ eclipse.shadow_axis_distance.km # => 2222.37
|
|
|
50
50
|
|
|
51
51
|
## Phases
|
|
52
52
|
|
|
53
|
-
An eclipse exposes its phases as `Astronoby::
|
|
53
|
+
An eclipse exposes its phases as `Astronoby::LunarEclipsePhase` objects, each with a `#starting_instant`, an `#ending_instant`, and a `#duration` (an `Astronoby::Duration`). The penumbral phase is always present. The partial phase is present for partial and total eclipses, and the total phase (totality) only for total eclipses. A phase that does not occur is `nil`.
|
|
54
54
|
|
|
55
55
|
```rb
|
|
56
|
-
eclipse.penumbral.starting_instant.to_time # => 2025-03-14 03:57:
|
|
57
|
-
eclipse.partial.starting_instant.to_time # => 2025-03-14 05:09:
|
|
58
|
-
eclipse.total.starting_instant.to_time # => 2025-03-14 06:26:
|
|
59
|
-
eclipse.total.ending_instant.to_time # => 2025-03-14 07:31:
|
|
56
|
+
eclipse.penumbral.starting_instant.to_time # => 2025-03-14 03:57:28 UTC (P1)
|
|
57
|
+
eclipse.partial.starting_instant.to_time # => 2025-03-14 05:09:36 UTC (U1)
|
|
58
|
+
eclipse.total.starting_instant.to_time # => 2025-03-14 06:26:02 UTC (U2)
|
|
59
|
+
eclipse.total.ending_instant.to_time # => 2025-03-14 07:31:29 UTC (U3)
|
|
60
60
|
eclipse.partial.ending_instant.to_time # => 2025-03-14 08:47:55 UTC (U4)
|
|
61
61
|
eclipse.penumbral.ending_instant.to_time # => 2025-03-14 10:00:08 UTC (P4)
|
|
62
62
|
|
|
63
|
-
eclipse.total.duration.seconds # =>
|
|
63
|
+
eclipse.total.duration.seconds # => 3927.11 (seconds of totality)
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
For a penumbral eclipse, `#partial` and `#total` are `nil`. For a partial eclipse, `#total` is `nil`.
|
|
67
67
|
|
|
68
|
+
## Shadow geometry
|
|
69
|
+
|
|
70
|
+
`Astronoby::LunarEclipseGeometry` describes Earth's shadow and the Moon's place in it at one instant. The eclipse carries the geometry at greatest eclipse, and each phase carries the geometry at its two contacts, so all six contacts of a total eclipse are reachable as well as greatest eclipse. It gives the size of the two shadow cones, how far the Moon's centre is from the shadow axis, and where on the Moon's limb the shadow bites.
|
|
71
|
+
|
|
72
|
+
```rb
|
|
73
|
+
geometry = eclipse.geometry
|
|
74
|
+
|
|
75
|
+
geometry.umbra_angular_radius.degrees * 60 # => 39.23 (arcminutes)
|
|
76
|
+
geometry.penumbra_angular_radius.degrees * 60 # => 71.39
|
|
77
|
+
geometry.angular_axis_distance.degrees * 60 # => 19.03
|
|
78
|
+
geometry.position_angle.degrees # => 29.07
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The cone radii are measured in the plane perpendicular to the shadow axis at the Moon's distance, which is where they are published. They are available as an `Astronoby::Distance` through `#umbra_radius` and `#penumbra_radius`, and as an `Astronoby::Angle` through `#umbra_angular_radius` and `#penumbra_angular_radius`. `#moon_distance` is the geocentric distance of the Moon, the distance of that plane.
|
|
82
|
+
|
|
83
|
+
`#position_angle` is the position angle of the contact point on the Moon's limb, seen from the Moon's centre, from celestial north through east. This is the quantity IMCCE and NASA publish. At greatest eclipse, where there is no contact point, it is the direction from the shadow axis to the Moon, which is what they report there too.
|
|
84
|
+
|
|
85
|
+
`#umbral_magnitude` and `#penumbral_magnitude` are available at any of these instants, not only at greatest eclipse. Each one is 0 at the contacts that define it, so the penumbral magnitude at P1 and P4 and the umbral magnitude at U1 and U4. The umbral magnitude is 1 at U2 and U3, where the Moon is wholly inside the umbra. The penumbral magnitude is well past 1 by then, since the Moon is far deeper into the penumbra than into the umbra.
|
|
86
|
+
|
|
87
|
+
```rb
|
|
88
|
+
eclipse.penumbral.starting_geometry.penumbral_magnitude # => 0.0 (P1)
|
|
89
|
+
eclipse.partial.starting_geometry.umbral_magnitude # => 0.0 (U1)
|
|
90
|
+
eclipse.total.starting_geometry.umbral_magnitude # => 1.0 (U2)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```rb
|
|
94
|
+
eclipse.penumbral.starting_geometry.position_angle.degrees # => 131.8 (P1)
|
|
95
|
+
eclipse.total.starting_geometry.position_angle.degrees # => 350.46 (U2)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Note that IMCCE labels the end of the penumbral phase `P2`, where this documentation uses `P4`.
|
|
99
|
+
|
|
68
100
|
```rb
|
|
69
101
|
penumbral_eclipse = Astronoby::Moon.eclipse_events(
|
|
70
102
|
ephem: ephem,
|
|
@@ -77,9 +109,92 @@ penumbral_eclipse.partial # => nil
|
|
|
77
109
|
penumbral_eclipse.total # => nil
|
|
78
110
|
```
|
|
79
111
|
|
|
112
|
+
## Local circumstances
|
|
113
|
+
|
|
114
|
+
The eclipse is the same everywhere, so there is no path of visibility to compute. All that changes from place to place is whether the Moon is above the horizon, and visibility is the intersection of the eclipse with the time the Moon spends up. Half the Earth sees an eclipse; the other half is in daylight, with the Moon below the horizon.
|
|
115
|
+
|
|
116
|
+
`LunarEclipse#visibility_from` takes an `Astronoby::Observer` and returns an `Astronoby::LunarEclipseVisibility`. No ephemeris is involved: the Moon's place at every contact is already carried by the geometry, so an eclipse can be observed from as many places as wanted, including after a round trip through a cache.
|
|
117
|
+
|
|
118
|
+
```rb
|
|
119
|
+
paris = Astronoby::Observer.new(
|
|
120
|
+
latitude: Astronoby::Angle.from_degrees(48.8566),
|
|
121
|
+
longitude: Astronoby::Angle.from_degrees(2.3522)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
visibility = eclipse.visibility_from(paris)
|
|
125
|
+
|
|
126
|
+
visibility.visible? # => true
|
|
127
|
+
visibility.entirely_visible? # => false
|
|
128
|
+
visibility.circumstance # => :ends_after_moonset
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`#circumstance` sums up what the place gets of the eclipse:
|
|
132
|
+
|
|
133
|
+
- `:not_visible`, the Moon is below the horizon for the whole eclipse
|
|
134
|
+
- `:entirely_visible`, the Moon is up from the first to the last penumbral contact
|
|
135
|
+
- `:begins_before_moonrise`, the eclipse is already under way when the Moon rises
|
|
136
|
+
- `:ends_after_moonset`, the eclipse is still under way when the Moon sets
|
|
137
|
+
- `:partially_visible`, anything else, which at high latitudes can be the Moon rising and setting within the eclipse
|
|
138
|
+
|
|
139
|
+
`#coverage` takes a phase and answers how much of it happens with the Moon up, in the same three words `#circumstance` uses of the whole eclipse: `:entirely_visible`, `:partially_visible`, `:not_visible`, or `nil` when the eclipse has no such phase. In the example, in Paris, the Moon sets during the partial phase, before totality begins.
|
|
140
|
+
|
|
141
|
+
```rb
|
|
142
|
+
visibility.coverage(:penumbral) # => :partially_visible
|
|
143
|
+
visibility.coverage(:partial) # => :partially_visible
|
|
144
|
+
visibility.coverage(:total) # => :not_visible
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`#horizon_crossings` are the instants of the eclipse at which the Moon rises or sets, and `#observable_windows` are the stretches of the eclipse spent with the Moon up. There is one window for an eclipse seen whole or in part, none for one not seen at all, and more than one only where the Moon crosses the horizon repeatedly.
|
|
148
|
+
|
|
149
|
+
```rb
|
|
150
|
+
visibility.horizon_crossings.map { |instant| instant.to_time }
|
|
151
|
+
# => [2025-03-14 06:11:41 UTC]
|
|
152
|
+
|
|
153
|
+
visibility.observable_windows.map { |window| [window.first.to_time, window.last.to_time] }
|
|
154
|
+
# => [[2025-03-14 03:57:28 UTC, 2025-03-14 06:11:41 UTC]]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
From Mexico City the same eclipse runs from end to end with the Moon high in the sky.
|
|
158
|
+
|
|
159
|
+
```rb
|
|
160
|
+
mexico_city = Astronoby::Observer.new(
|
|
161
|
+
latitude: Astronoby::Angle.from_degrees(19.4326),
|
|
162
|
+
longitude: Astronoby::Angle.from_degrees(-99.1332)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
visibility = eclipse.visibility_from(mexico_city)
|
|
166
|
+
|
|
167
|
+
visibility.circumstance # => :entirely_visible
|
|
168
|
+
visibility.coverage(:total) # => :entirely_visible
|
|
169
|
+
visibility.horizon_crossings # => []
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`#above_horizon_at?` answers whether the Moon is up at an instant of the eclipse, and raises outside it.
|
|
173
|
+
|
|
174
|
+
```rb
|
|
175
|
+
visibility.above_horizon_at?(eclipse.total.starting_instant) # => true
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Visibility answers whether the Moon is up, not how high it is. The Moon's place at each contact is interpolated from what the eclipse carries, which is what lets an eclipse be observed from anywhere without an ephemeris, and that is accurate well past what the horizon convention can resolve. For the altitude itself, ask the Moon, which computes it from the ephemeris with no interpolation at all.
|
|
179
|
+
|
|
180
|
+
```rb
|
|
181
|
+
Astronoby::Moon
|
|
182
|
+
.new(ephem: ephem, instant: eclipse.total.starting_instant)
|
|
183
|
+
.observed_by(mexico_city)
|
|
184
|
+
.horizontal
|
|
185
|
+
.altitude
|
|
186
|
+
.degrees # => 72.45
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The Moon counts as up once its upper limb clears the refracted horizon, which is the convention Astronoby uses for moonrise and moonset, through `Astronoby::Horizon`.
|
|
190
|
+
|
|
80
191
|
## Precision
|
|
81
192
|
|
|
82
|
-
Earth's shadow is enlarged by its atmosphere
|
|
193
|
+
Two conventions shape the result. Earth's shadow is enlarged by its atmosphere, so Astronoby adds a 64 km shell to Earth's radius before building the shadow cones. And eclipse contacts are reduced with the IAU eclipse constant k, which puts the Moon's radius at 1738.09 km, slightly above its physical equatorial radius. Both are calibrated against IMCCE.
|
|
194
|
+
|
|
195
|
+
Validated against IMCCE, the eclipse kind and the magnitudes match, and the contacts agree to within a couple of seconds. The shadow radii and the distance from the shadow axis match to well under an arcsecond, and position angles to 0.03 degrees.
|
|
196
|
+
|
|
197
|
+
The local circumstances are reduced without the ephemeris, from the Moon's place at the contacts and the sidereal rate. The altitudes agree with the topocentric places computed from the ephemeris to under an arcminute, and the horizon crossings put the Moon on the horizon to a few arcseconds, which is well inside the uncertainty of the fixed refraction the convention assumes.
|
|
83
198
|
|
|
84
199
|
## See also
|
|
85
200
|
|
data/docs/twilight_times.md
CHANGED
|
@@ -77,13 +77,13 @@ events = calculator.events_between(
|
|
|
77
77
|
|
|
78
78
|
events.morning_civil_twilight_times
|
|
79
79
|
# =>
|
|
80
|
-
# [2025-08-01 02:29:
|
|
81
|
-
# 2025-08-02 02:30:
|
|
82
|
-
# 2025-08-03 02:31:
|
|
83
|
-
# 2025-08-04 02:32:
|
|
84
|
-
# 2025-08-05 02:33:
|
|
85
|
-
# 2025-08-06 02:34:
|
|
86
|
-
# 2025-08-07 02:35:
|
|
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]
|
|
87
87
|
```
|
|
88
88
|
|
|
89
89
|
## `#event_on`
|
|
@@ -104,23 +104,38 @@ event.morning_astronomical_twilight_time
|
|
|
104
104
|
# => 2025-05-01 01:16:21 UTC
|
|
105
105
|
|
|
106
106
|
event.morning_nautical_twilight_time
|
|
107
|
-
# => 2025-05-01 01:55:
|
|
107
|
+
# => 2025-05-01 01:55:49 UTC
|
|
108
108
|
|
|
109
109
|
event.evening_civil_twilight_time
|
|
110
|
-
# => 2025-05-01 17:30:
|
|
110
|
+
# => 2025-05-01 17:30:50 UTC
|
|
111
111
|
|
|
112
112
|
event.evening_nautical_twilight_time
|
|
113
|
-
# => 2025-05-01 18:07:
|
|
113
|
+
# => 2025-05-01 18:07:27 UTC
|
|
114
114
|
|
|
115
115
|
event.evening_astronomical_twilight_time
|
|
116
|
-
# => 2025-05-01 18:
|
|
116
|
+
# => 2025-05-01 18:47:10 UTC
|
|
117
117
|
```
|
|
118
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
|
+
|
|
119
133
|
[Ephem page]: ephem.md
|
|
120
134
|
[Observer page]: observer.md
|
|
121
135
|
|
|
122
136
|
## See also
|
|
123
137
|
|
|
138
|
+
- [Altitude Crossings](altitude_crossings.md) - for any altitude, unrounded
|
|
124
139
|
- [Rise, Transit and Set Times](rise_transit_set_times.md) - for sun and moon events
|
|
125
140
|
- [Observer](observer.md) - for location setup
|
|
126
141
|
- [Ephemerides](ephem.md) - for data sources
|
|
@@ -113,30 +113,12 @@ module Astronoby
|
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
def compute_apparent
|
|
116
|
-
@apparent =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
123
|
-
else
|
|
124
|
-
precession_matrix = Precession.matrix_for(@instant)
|
|
125
|
-
nutation_matrix = Nutation.matrix_for(@instant)
|
|
126
|
-
corrected_position = Distance.vector_from_meters(
|
|
127
|
-
precession_matrix * nutation_matrix * astrometric.position.map(&:m)
|
|
128
|
-
)
|
|
129
|
-
corrected_velocity = Velocity.vector_from_mps(
|
|
130
|
-
precession_matrix * nutation_matrix * astrometric.velocity.map(&:mps)
|
|
131
|
-
)
|
|
132
|
-
Apparent.new(
|
|
133
|
-
position: corrected_position,
|
|
134
|
-
velocity: corrected_velocity,
|
|
135
|
-
instant: @instant,
|
|
136
|
-
center: Center.geocentric,
|
|
137
|
-
target_body: body
|
|
138
|
-
)
|
|
139
|
-
end
|
|
116
|
+
@apparent = Apparent.build_from_astrometric(
|
|
117
|
+
instant: @instant,
|
|
118
|
+
target_astrometric: astrometric,
|
|
119
|
+
earth_geometric: @earth_geometric,
|
|
120
|
+
target_body: body
|
|
121
|
+
)
|
|
140
122
|
end
|
|
141
123
|
end
|
|
142
124
|
end
|
data/lib/astronoby/cache.rb
CHANGED
|
@@ -175,6 +175,19 @@ module Astronoby
|
|
|
175
175
|
[type, rounded_tt, *components]
|
|
176
176
|
end
|
|
177
177
|
|
|
178
|
+
# Generate a cache key for a UTC time, without rounding. Sidereal time
|
|
179
|
+
# moves by about 15 arcseconds per second, so unlike nutation it can't
|
|
180
|
+
# share an entry with a nearby instant.
|
|
181
|
+
# @param type [Symbol] The calculation type (e.g., :gmst)
|
|
182
|
+
# @param utc [Time] The UTC time
|
|
183
|
+
# @param components [Array] Additional components for the key
|
|
184
|
+
# @return [Array, nil] The complete cache key, nil if cache is disabled
|
|
185
|
+
def generate_for_utc(type, utc, *components)
|
|
186
|
+
return nil unless Astronoby.configuration.cache_enabled?
|
|
187
|
+
|
|
188
|
+
[type, utc.to_r, *components]
|
|
189
|
+
end
|
|
190
|
+
|
|
178
191
|
# Round terrestrial time to specified precision
|
|
179
192
|
# @param tt [Numeric] Terrestrial time value
|
|
180
193
|
# @param precision [Numeric] Precision in seconds
|
data/lib/astronoby/constants.rb
CHANGED
|
@@ -36,6 +36,7 @@ module Astronoby
|
|
|
36
36
|
|
|
37
37
|
# WGS84 Earth Constants
|
|
38
38
|
WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS = 6378137
|
|
39
|
+
IAU_MOON_RADIUS_IN_METERS = 0.2725076 * WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS
|
|
39
40
|
WGS84_INVERSE_FLATTENING = 298.257223563
|
|
40
41
|
WGS84_FLATTENING = 1.0 / WGS84_INVERSE_FLATTENING
|
|
41
42
|
WGS84_ECCENTICITY_SQUARED = 2.0 * WGS84_FLATTENING - WGS84_FLATTENING * WGS84_FLATTENING
|
|
@@ -45,7 +46,5 @@ module Astronoby
|
|
|
45
46
|
EARTH_FLATTENING_CORRECTION = 0.996647
|
|
46
47
|
|
|
47
48
|
EARTH_ANGULAR_VELOCITY_RAD_PER_S = 7.2921159e-5
|
|
48
|
-
|
|
49
|
-
TAI_TT_OFFSET = 32.184
|
|
50
49
|
end
|
|
51
50
|
end
|
|
@@ -32,17 +32,23 @@ module Astronoby
|
|
|
32
32
|
@epoch = epoch
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# @param epoch [Numeric] Julian Date epoch the coordinates are referred to
|
|
35
36
|
# @return [Astronoby::Coordinates::Equatorial] zero coordinates
|
|
36
|
-
def self.zero
|
|
37
|
-
new(
|
|
37
|
+
def self.zero(epoch: JulianDate::DEFAULT_EPOCH)
|
|
38
|
+
new(
|
|
39
|
+
declination: Angle.zero,
|
|
40
|
+
right_ascension: Angle.zero,
|
|
41
|
+
epoch: epoch
|
|
42
|
+
)
|
|
38
43
|
end
|
|
39
44
|
|
|
40
45
|
# Derives equatorial coordinates from a position vector.
|
|
41
46
|
#
|
|
42
47
|
# @param position [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
48
|
+
# @param epoch [Numeric] Julian Date epoch the vector is referred to
|
|
43
49
|
# @return [Astronoby::Coordinates::Equatorial] equatorial coordinates
|
|
44
|
-
def self.from_position_vector(position)
|
|
45
|
-
return zero if position.zero?
|
|
50
|
+
def self.from_position_vector(position, epoch: JulianDate::DEFAULT_EPOCH)
|
|
51
|
+
return zero(epoch: epoch) if position.zero?
|
|
46
52
|
|
|
47
53
|
term1 = position.z.m
|
|
48
54
|
term2 = position.magnitude.m
|
|
@@ -58,7 +64,11 @@ module Astronoby
|
|
|
58
64
|
angle
|
|
59
65
|
)
|
|
60
66
|
|
|
61
|
-
new(
|
|
67
|
+
new(
|
|
68
|
+
declination: declination,
|
|
69
|
+
right_ascension: right_ascension,
|
|
70
|
+
epoch: epoch
|
|
71
|
+
)
|
|
62
72
|
end
|
|
63
73
|
|
|
64
74
|
# Computes the hour angle for a given time and observer longitude.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "iers"
|
|
4
|
+
|
|
5
|
+
module Astronoby
|
|
6
|
+
module DeltaT
|
|
7
|
+
class << self
|
|
8
|
+
# @param instant [Numeric, Time, Date, DateTime] a Julian Date, or a date
|
|
9
|
+
# and time. Any time scale will do.
|
|
10
|
+
# @return [Numeric] ΔT in seconds
|
|
11
|
+
# @raise [Astronoby::IncompatibleArgumentsError] if the instant is not a
|
|
12
|
+
# Numeric, Time, Date or DateTime
|
|
13
|
+
def at(instant)
|
|
14
|
+
case instant
|
|
15
|
+
when Numeric
|
|
16
|
+
IERS::DeltaT.at(jd: instant).delta_t
|
|
17
|
+
when ::Time, ::Date, ::DateTime
|
|
18
|
+
IERS::DeltaT.at(instant).delta_t
|
|
19
|
+
else
|
|
20
|
+
raise IncompatibleArgumentsError,
|
|
21
|
+
"Expected a Numeric, Time, Date or DateTime object, got #{instant.class}"
|
|
22
|
+
end
|
|
23
|
+
rescue IERS::OutOfRangeError => error
|
|
24
|
+
outside_available_range(error)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# IERS covers 1800 up to the end of its EOP series, and refuses anything
|
|
30
|
+
# outside. Rather than propagate that, we answer with the closest value
|
|
31
|
+
# we have: 0 before 1800, where ΔT is small next to the uncertainty on
|
|
32
|
+
# it, and the last measured value after the series, which drifts slowly
|
|
33
|
+
# enough to beat any extrapolation we could invent.
|
|
34
|
+
def outside_available_range(error)
|
|
35
|
+
last_measured = IERS::Data.finals_entries.last
|
|
36
|
+
return 0 if last_measured.nil?
|
|
37
|
+
return 0 if error.requested_mjd.nil?
|
|
38
|
+
return 0 if error.requested_mjd < last_measured.mjd
|
|
39
|
+
|
|
40
|
+
IERS::DeltaT.at(mjd: last_measured.mjd).delta_t
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# A moment when a body's altitude passes through a given value.
|
|
5
|
+
class AltitudeCrossing
|
|
6
|
+
# @return [Astronoby::Instant] when the crossing happens, unrounded
|
|
7
|
+
attr_reader :instant
|
|
8
|
+
|
|
9
|
+
# @return [Symbol] +:rising+ when the altitude increases through the
|
|
10
|
+
# target, +:setting+ when it decreases
|
|
11
|
+
attr_reader :direction
|
|
12
|
+
|
|
13
|
+
# @param instant [Astronoby::Instant] when the crossing happens
|
|
14
|
+
# @param direction [Symbol] +:rising+ or +:setting+
|
|
15
|
+
# @param grazing [Boolean] whether the body only just reaches the target
|
|
16
|
+
def initialize(instant:, direction:, grazing: false)
|
|
17
|
+
@instant = instant
|
|
18
|
+
@direction = direction
|
|
19
|
+
@grazing = grazing
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Boolean] true when the target is within a few hundredths of a
|
|
23
|
+
# degree of the body's highest or lowest altitude, so the crossing is
|
|
24
|
+
# nearly tangent and its instant is sensitive to small errors
|
|
25
|
+
def grazing?
|
|
26
|
+
@grazing
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|