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
|
@@ -21,17 +21,18 @@ module Astronoby
|
|
|
21
21
|
# Authors: Sean E. Urban and P. Kenneth Seidelmann
|
|
22
22
|
# Chapter: 11 - Eclipses of the Sun and Moon
|
|
23
23
|
class LunarEclipseCalculator
|
|
24
|
-
# Atmospheric enlargement of Earth's shadow (Danjon-style):
|
|
25
|
-
#
|
|
26
|
-
# umbra and the penumbra.
|
|
27
|
-
# uses the same INPOP19A ephemeris
|
|
28
|
-
#
|
|
29
|
-
|
|
24
|
+
# Atmospheric enlargement of Earth's shadow (Danjon-style): the shell of
|
|
25
|
+
# atmosphere added to Earth's radius before the shadow cones are built, which
|
|
26
|
+
# propagates into both the umbra and the penumbra. Calibrated against IMCCE,
|
|
27
|
+
# which uses the same INPOP19A ephemeris, over the eclipses from 2026 to
|
|
28
|
+
# 2048. It is well below the textbook values, which put the shell at 75 km
|
|
29
|
+
# (Danjon) or 128 km (Chauvenet).
|
|
30
|
+
SHADOW_ATMOSPHERE_KM = 64.0
|
|
30
31
|
|
|
31
32
|
SUN_RADIUS_KM = Sun::EQUATORIAL_RADIUS.km
|
|
32
|
-
MOON_RADIUS_KM = Moon::EQUATORIAL_RADIUS.km
|
|
33
33
|
EARTH_RADIUS_KM =
|
|
34
34
|
Constants::WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS / 1000.0
|
|
35
|
+
MOON_RADIUS_KM = Constants::IAU_MOON_RADIUS_IN_METERS / 1000.0
|
|
35
36
|
|
|
36
37
|
# Largest distance of the Moon's centre from the shadow axis, in Earth radii,
|
|
37
38
|
# at which any (penumbral) eclipse is still possible is about 1.57. Full moons
|
|
@@ -53,6 +54,15 @@ module Astronoby
|
|
|
53
54
|
# well below the one-second resolution the contacts are reported at.
|
|
54
55
|
CONTACT_TOLERANCE = 1e-7
|
|
55
56
|
|
|
57
|
+
# Which limb of the Moon a phase boundary touches the shadow cone with. The
|
|
58
|
+
# penumbral and partial phases are bounded by external tangencies, where the
|
|
59
|
+
# contact point is on the limb facing the shadow axis; the total phase by
|
|
60
|
+
# internal ones, where it is on the far limb. Greatest eclipse has no
|
|
61
|
+
# contact point, and IMCCE reports the axis-to-Moon direction there.
|
|
62
|
+
EXTERNAL_TANGENCY = :external
|
|
63
|
+
INTERNAL_TANGENCY = :internal
|
|
64
|
+
NO_TANGENCY = :none
|
|
65
|
+
|
|
56
66
|
# Geometry of the Sun, Moon and Earth's shadow at one instant, in kilometres
|
|
57
67
|
# in the plane perpendicular to the shadow axis at the Moon's distance.
|
|
58
68
|
class Geometry
|
|
@@ -65,17 +75,86 @@ module Astronoby
|
|
|
65
75
|
# @return [Float] radius of the penumbra at the Moon's distance (km)
|
|
66
76
|
attr_reader :penumbra_radius
|
|
67
77
|
|
|
68
|
-
# @return [
|
|
69
|
-
attr_reader :
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
# @return [Boolean] whether the Moon is north of the shadow axis
|
|
79
|
+
attr_reader :north_of_axis
|
|
80
|
+
|
|
81
|
+
# @return [Float] position angle of the Moon's centre from the shadow
|
|
82
|
+
# axis, in radians from celestial north through east
|
|
83
|
+
attr_reader :position_angle
|
|
84
|
+
|
|
85
|
+
# @return [Float] geocentric distance of the Moon (km)
|
|
86
|
+
attr_reader :moon_distance
|
|
87
|
+
|
|
88
|
+
# @return [Float] x component of the Moon's apparent geocentric unit
|
|
89
|
+
# vector, in the true equator and equinox of date
|
|
90
|
+
attr_reader :moon_unit_x
|
|
91
|
+
|
|
92
|
+
# @return [Float] y component of the Moon's apparent geocentric unit
|
|
93
|
+
# vector, in the true equator and equinox of date
|
|
94
|
+
attr_reader :moon_unit_y
|
|
95
|
+
|
|
96
|
+
# @return [Float] z component of the Moon's apparent geocentric unit
|
|
97
|
+
# vector, in the true equator and equinox of date
|
|
98
|
+
attr_reader :moon_unit_z
|
|
99
|
+
|
|
100
|
+
# @return [Numeric] Julian Date (TT) the geometry was built at, which is
|
|
101
|
+
# the epoch its equator and equinox are referred to
|
|
102
|
+
attr_reader :epoch
|
|
103
|
+
|
|
104
|
+
def initialize(
|
|
105
|
+
axis_distance:,
|
|
106
|
+
umbra_radius:,
|
|
107
|
+
penumbra_radius:,
|
|
108
|
+
north_of_axis:,
|
|
109
|
+
position_angle:,
|
|
110
|
+
moon_distance:,
|
|
111
|
+
moon_unit_x:,
|
|
112
|
+
moon_unit_y:,
|
|
113
|
+
moon_unit_z:,
|
|
114
|
+
epoch:
|
|
115
|
+
)
|
|
72
116
|
@axis_distance = axis_distance
|
|
73
117
|
@umbra_radius = umbra_radius
|
|
74
118
|
@penumbra_radius = penumbra_radius
|
|
75
|
-
@
|
|
119
|
+
@north_of_axis = north_of_axis
|
|
120
|
+
@position_angle = position_angle
|
|
121
|
+
@moon_distance = moon_distance
|
|
122
|
+
@moon_unit_x = moon_unit_x
|
|
123
|
+
@moon_unit_y = moon_unit_y
|
|
124
|
+
@moon_unit_z = moon_unit_z
|
|
125
|
+
@epoch = epoch
|
|
76
126
|
freeze
|
|
77
127
|
end
|
|
78
128
|
|
|
129
|
+
def to_lunar_eclipse_geometry(tangency)
|
|
130
|
+
angle = if tangency == EXTERNAL_TANGENCY
|
|
131
|
+
(position_angle + Math::PI) % Constants::RADIANS_PER_CIRCLE
|
|
132
|
+
else
|
|
133
|
+
position_angle
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
LunarEclipseGeometry.new(
|
|
137
|
+
axis_distance: Distance.from_kilometers(axis_distance),
|
|
138
|
+
position_angle: Angle.from_radians(angle),
|
|
139
|
+
umbra_radius: Distance.from_kilometers(umbra_radius),
|
|
140
|
+
penumbra_radius: Distance.from_kilometers(penumbra_radius),
|
|
141
|
+
moon_distance: Distance.from_kilometers(moon_distance),
|
|
142
|
+
moon_coordinates: moon_coordinates,
|
|
143
|
+
north_of_axis: north_of_axis
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def moon_coordinates
|
|
148
|
+
Coordinates::Equatorial.new(
|
|
149
|
+
right_ascension: Angle.from_radians(
|
|
150
|
+
Math.atan2(moon_unit_y, moon_unit_x) %
|
|
151
|
+
Constants::RADIANS_PER_CIRCLE
|
|
152
|
+
),
|
|
153
|
+
declination: Angle.asin(moon_unit_z.clamp(-1.0, 1.0)),
|
|
154
|
+
epoch: epoch
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
79
158
|
def umbral_magnitude
|
|
80
159
|
(umbra_radius + MOON_RADIUS_KM - axis_distance) / (2 * MOON_RADIUS_KM)
|
|
81
160
|
end
|
|
@@ -99,6 +178,8 @@ module Astronoby
|
|
|
99
178
|
end
|
|
100
179
|
end
|
|
101
180
|
|
|
181
|
+
private_constant :Geometry
|
|
182
|
+
|
|
102
183
|
# @param ephem [::Ephem::SPK] ephemeris data source
|
|
103
184
|
def initialize(ephem:)
|
|
104
185
|
@ephem = ephem
|
|
@@ -177,19 +258,20 @@ module Astronoby
|
|
|
177
258
|
|
|
178
259
|
def eclipse_at(greatest_jd)
|
|
179
260
|
geometry = geometry_at(greatest_jd)
|
|
180
|
-
penumbral = phase_for(
|
|
261
|
+
penumbral = phase_for(
|
|
262
|
+
greatest_jd,
|
|
263
|
+
EXTERNAL_TANGENCY,
|
|
264
|
+
&:penumbral_contact_value
|
|
265
|
+
)
|
|
181
266
|
return nil if penumbral.nil?
|
|
182
267
|
|
|
183
|
-
partial = phase_for(greatest_jd, &:partial_contact_value)
|
|
184
|
-
total = phase_for(greatest_jd, &:total_contact_value)
|
|
268
|
+
partial = phase_for(greatest_jd, EXTERNAL_TANGENCY, &:partial_contact_value)
|
|
269
|
+
total = phase_for(greatest_jd, INTERNAL_TANGENCY, &:total_contact_value)
|
|
185
270
|
|
|
186
271
|
LunarEclipse.new(
|
|
187
272
|
instant: Instant.from_terrestrial_time(greatest_jd),
|
|
188
273
|
kind: kind_for(partial, total),
|
|
189
|
-
|
|
190
|
-
penumbral_magnitude: geometry.penumbral_magnitude,
|
|
191
|
-
gamma: geometry.gamma,
|
|
192
|
-
shadow_axis_distance: Distance.from_kilometers(geometry.axis_distance),
|
|
274
|
+
geometry: geometry.to_lunar_eclipse_geometry(NO_TANGENCY),
|
|
193
275
|
penumbral: penumbral,
|
|
194
276
|
partial: partial,
|
|
195
277
|
total: total
|
|
@@ -212,7 +294,7 @@ module Astronoby
|
|
|
212
294
|
# corresponding edge of the window, found by bisection. This is robust to
|
|
213
295
|
# arbitrarily short phases (a barely-total or grazing eclipse), unlike a
|
|
214
296
|
# fixed-resolution scan that can step over a brief crossing.
|
|
215
|
-
def phase_for(greatest_jd, &contact_value)
|
|
297
|
+
def phase_for(greatest_jd, tangency, &contact_value)
|
|
216
298
|
value_at = ->(jd) { contact_value.call(geometry_at(jd)) }
|
|
217
299
|
return nil unless value_at.call(greatest_jd).negative?
|
|
218
300
|
|
|
@@ -228,15 +310,23 @@ module Astronoby
|
|
|
228
310
|
)
|
|
229
311
|
return nil unless starting && ending
|
|
230
312
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
313
|
+
starting_jd, starting_geometry = starting
|
|
314
|
+
ending_jd, ending_geometry = ending
|
|
315
|
+
|
|
316
|
+
LunarEclipsePhase.new(
|
|
317
|
+
starting_instant: Instant.from_terrestrial_time(starting_jd),
|
|
318
|
+
ending_instant: Instant.from_terrestrial_time(ending_jd),
|
|
319
|
+
starting_geometry:
|
|
320
|
+
starting_geometry.to_lunar_eclipse_geometry(tangency),
|
|
321
|
+
ending_geometry: ending_geometry.to_lunar_eclipse_geometry(tangency)
|
|
234
322
|
)
|
|
235
323
|
end
|
|
236
324
|
|
|
237
325
|
# Bisects for the single contact between +outside_jd+ (value positive, the
|
|
238
326
|
# Moon outside the boundary) and +inside_jd+ (value negative, at greatest
|
|
239
|
-
# eclipse). Returns
|
|
327
|
+
# eclipse). Returns the contact date, taken as the midpoint of the final
|
|
328
|
+
# bracket, and the geometry already computed at that bracket's inner end, at
|
|
329
|
+
# most 2 ms away. Returns nil if the boundary is not crossed in the window.
|
|
240
330
|
def bisect_contact(value_at, outside_jd, inside_jd)
|
|
241
331
|
return nil unless value_at.call(outside_jd).positive?
|
|
242
332
|
|
|
@@ -248,7 +338,7 @@ module Astronoby
|
|
|
248
338
|
outside_jd = midpoint
|
|
249
339
|
end
|
|
250
340
|
end
|
|
251
|
-
(outside_jd + inside_jd) / 2.0
|
|
341
|
+
[(outside_jd + inside_jd) / 2.0, geometry_at(inside_jd)]
|
|
252
342
|
end
|
|
253
343
|
|
|
254
344
|
# Builds the geometry at a Julian Date (TT) from the apparent geocentric
|
|
@@ -262,22 +352,44 @@ module Astronoby
|
|
|
262
352
|
|
|
263
353
|
moon_distance = moon.distance.km
|
|
264
354
|
sun_distance = sun.distance.km
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
355
|
+
|
|
356
|
+
moon_x, moon_y, moon_z =
|
|
357
|
+
moon.position.to_a.map { |component| component.km / moon_distance }
|
|
358
|
+
axis_x, axis_y, axis_z =
|
|
359
|
+
sun.position.to_a.map { |component| -component.km / sun_distance }
|
|
360
|
+
|
|
361
|
+
hypotenuse = Math.sqrt(axis_x * axis_x + axis_y * axis_y)
|
|
362
|
+
east_x = -axis_y / hypotenuse
|
|
363
|
+
east_y = axis_x / hypotenuse
|
|
364
|
+
north_x = -axis_z * east_y
|
|
365
|
+
north_y = axis_z * east_x
|
|
366
|
+
north_z = axis_x * east_y - axis_y * east_x
|
|
367
|
+
|
|
368
|
+
east = moon_x * east_x + moon_y * east_y
|
|
369
|
+
north = moon_x * north_x + moon_y * north_y + moon_z * north_z
|
|
370
|
+
cosine = moon_x * axis_x + moon_y * axis_y + moon_z * axis_z
|
|
371
|
+
sine = Math.sqrt(east * east + north * north)
|
|
372
|
+
|
|
373
|
+
axial_distance = moon_distance * cosine
|
|
374
|
+
perpendicular_distance = moon_distance * sine
|
|
268
375
|
|
|
269
376
|
# Danjon enlargement: enlarge Earth's radius before building the cones.
|
|
270
|
-
earth_radius = EARTH_RADIUS_KM
|
|
377
|
+
earth_radius = EARTH_RADIUS_KM + SHADOW_ATMOSPHERE_KM
|
|
271
378
|
umbra_half_angle_tangent = (SUN_RADIUS_KM - earth_radius) / sun_distance
|
|
272
379
|
penumbra_half_angle_tangent =
|
|
273
380
|
(SUN_RADIUS_KM + earth_radius) / sun_distance
|
|
274
|
-
latitude_sign = moon.ecliptic.latitude.degrees.negative? ? -1 : 1
|
|
275
381
|
|
|
276
382
|
Geometry.new(
|
|
277
383
|
axis_distance: perpendicular_distance,
|
|
278
384
|
umbra_radius: earth_radius - axial_distance * umbra_half_angle_tangent,
|
|
279
385
|
penumbra_radius: earth_radius + axial_distance * penumbra_half_angle_tangent,
|
|
280
|
-
|
|
386
|
+
north_of_axis: !north.negative?,
|
|
387
|
+
position_angle: Math.atan2(east, north) % Constants::RADIANS_PER_CIRCLE,
|
|
388
|
+
moon_distance: moon_distance,
|
|
389
|
+
moon_unit_x: moon_x,
|
|
390
|
+
moon_unit_y: moon_y,
|
|
391
|
+
moon_unit_z: moon_z,
|
|
392
|
+
epoch: jd
|
|
281
393
|
)
|
|
282
394
|
end
|
|
283
395
|
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
# The geometry of Earth's shadow and the Moon's place in it at one instant of
|
|
5
|
+
# a lunar eclipse.
|
|
6
|
+
#
|
|
7
|
+
# The shadow cones are cut by the plane perpendicular to the shadow axis at
|
|
8
|
+
# the Moon's distance, which is where their radii are published. Earth's
|
|
9
|
+
# radius is enlarged by its atmosphere before the cones are built, so these
|
|
10
|
+
# are the enlarged, observable radii.
|
|
11
|
+
class LunarEclipseGeometry
|
|
12
|
+
# @return [Astronoby::Distance] distance of the Moon's centre from the axis
|
|
13
|
+
# of Earth's shadow
|
|
14
|
+
attr_reader :axis_distance
|
|
15
|
+
|
|
16
|
+
# @return [Astronoby::Angle] position angle of the contact point on the
|
|
17
|
+
# Moon's limb, seen from the Moon's centre, from celestial north through
|
|
18
|
+
# east. This is the quantity IMCCE and NASA publish. At greatest eclipse,
|
|
19
|
+
# where there is no contact point, it is the direction from the shadow
|
|
20
|
+
# axis to the Moon, which is what they report there.
|
|
21
|
+
attr_reader :position_angle
|
|
22
|
+
|
|
23
|
+
# @return [Astronoby::Distance] radius of the umbra, the inner and total
|
|
24
|
+
# shadow, at the Moon's distance
|
|
25
|
+
attr_reader :umbra_radius
|
|
26
|
+
|
|
27
|
+
# @return [Astronoby::Distance] radius of the penumbra, the outer and
|
|
28
|
+
# partial shadow, at the Moon's distance
|
|
29
|
+
attr_reader :penumbra_radius
|
|
30
|
+
|
|
31
|
+
# @return [Astronoby::Distance] geocentric distance of the Moon, the
|
|
32
|
+
# distance of the plane the radii are measured in
|
|
33
|
+
attr_reader :moon_distance
|
|
34
|
+
|
|
35
|
+
# @return [Astronoby::Coordinates::Equatorial] apparent geocentric place
|
|
36
|
+
# of the Moon, referred to the true equator and equinox of date
|
|
37
|
+
attr_reader :moon_coordinates
|
|
38
|
+
|
|
39
|
+
# @return [Boolean] true when the Moon is north of the shadow axis
|
|
40
|
+
def north_of_axis?
|
|
41
|
+
@north_of_axis
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param axis_distance [Astronoby::Distance] distance from the shadow axis
|
|
45
|
+
# @param position_angle [Astronoby::Angle] position angle of the contact
|
|
46
|
+
# point on the Moon's limb, from celestial north through east
|
|
47
|
+
# @param umbra_radius [Astronoby::Distance] umbra radius
|
|
48
|
+
# @param penumbra_radius [Astronoby::Distance] penumbra radius
|
|
49
|
+
# @param moon_distance [Astronoby::Distance] geocentric distance of the Moon
|
|
50
|
+
# @param moon_coordinates [Astronoby::Coordinates::Equatorial] apparent
|
|
51
|
+
# geocentric place of the Moon, referred to the true equator and equinox
|
|
52
|
+
# of date
|
|
53
|
+
# @param north_of_axis [Boolean] whether the Moon is north of the shadow
|
|
54
|
+
# axis
|
|
55
|
+
def initialize(
|
|
56
|
+
axis_distance:,
|
|
57
|
+
position_angle:,
|
|
58
|
+
umbra_radius:,
|
|
59
|
+
penumbra_radius:,
|
|
60
|
+
moon_distance:,
|
|
61
|
+
moon_coordinates:,
|
|
62
|
+
north_of_axis:
|
|
63
|
+
)
|
|
64
|
+
@axis_distance = axis_distance
|
|
65
|
+
@position_angle = position_angle
|
|
66
|
+
@umbra_radius = umbra_radius
|
|
67
|
+
@penumbra_radius = penumbra_radius
|
|
68
|
+
@moon_distance = moon_distance
|
|
69
|
+
@moon_coordinates = moon_coordinates
|
|
70
|
+
@north_of_axis = north_of_axis
|
|
71
|
+
freeze
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Astronoby::Angle] angular distance of the Moon's centre from the
|
|
75
|
+
# axis of Earth's shadow
|
|
76
|
+
def angular_axis_distance
|
|
77
|
+
angular_radius_of(@axis_distance)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Astronoby::Angle] angular radius of the umbra
|
|
81
|
+
def umbra_angular_radius
|
|
82
|
+
angular_radius_of(@umbra_radius)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @return [Astronoby::Angle] angular radius of the penumbra
|
|
86
|
+
def penumbra_angular_radius
|
|
87
|
+
angular_radius_of(@penumbra_radius)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [Float] fraction of the Moon's diameter immersed in the umbra,
|
|
91
|
+
# negative when the Moon is clear of the umbra
|
|
92
|
+
def umbral_magnitude
|
|
93
|
+
magnitude_for(@umbra_radius)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# @return [Float] fraction of the Moon's diameter immersed in the penumbra,
|
|
97
|
+
# negative when the Moon is clear of the penumbra
|
|
98
|
+
def penumbral_magnitude
|
|
99
|
+
magnitude_for(@penumbra_radius)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def angular_radius_of(distance)
|
|
105
|
+
Angle.from_radians(Math.asin(distance.km / @moon_distance.km))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def magnitude_for(shadow_radius)
|
|
109
|
+
moon_radius = Constants::IAU_MOON_RADIUS_IN_METERS / 1000.0
|
|
110
|
+
(
|
|
111
|
+
shadow_radius.km + moon_radius - @axis_distance.km
|
|
112
|
+
) / (2 * moon_radius)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class LunarEclipsePhase
|
|
5
|
+
# @return [Astronoby::Instant] when the phase begins
|
|
6
|
+
attr_reader :starting_instant
|
|
7
|
+
|
|
8
|
+
# @return [Astronoby::Instant] when the phase ends
|
|
9
|
+
attr_reader :ending_instant
|
|
10
|
+
|
|
11
|
+
# @return [Astronoby::LunarEclipseGeometry] the shadow geometry when the
|
|
12
|
+
# phase begins
|
|
13
|
+
attr_reader :starting_geometry
|
|
14
|
+
|
|
15
|
+
# @return [Astronoby::LunarEclipseGeometry] the shadow geometry when the
|
|
16
|
+
# phase ends
|
|
17
|
+
attr_reader :ending_geometry
|
|
18
|
+
|
|
19
|
+
# @param starting_instant [Astronoby::Instant] when the phase begins
|
|
20
|
+
# @param ending_instant [Astronoby::Instant] when the phase ends
|
|
21
|
+
# @param starting_geometry [Astronoby::LunarEclipseGeometry] the geometry
|
|
22
|
+
# when the phase begins
|
|
23
|
+
# @param ending_geometry [Astronoby::LunarEclipseGeometry] the geometry when
|
|
24
|
+
# the phase ends
|
|
25
|
+
def initialize(
|
|
26
|
+
starting_instant:,
|
|
27
|
+
ending_instant:,
|
|
28
|
+
starting_geometry:,
|
|
29
|
+
ending_geometry:
|
|
30
|
+
)
|
|
31
|
+
@starting_instant = starting_instant
|
|
32
|
+
@ending_instant = ending_instant
|
|
33
|
+
@starting_geometry = starting_geometry
|
|
34
|
+
@ending_geometry = ending_geometry
|
|
35
|
+
freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @return [Astronoby::Duration] phase duration
|
|
39
|
+
def duration
|
|
40
|
+
Duration.from_seconds(
|
|
41
|
+
(@ending_instant.tt - @starting_instant.tt) * Constants::SECONDS_PER_DAY
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|