astronoby 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +87 -3
  4. data/README.md +56 -32
  5. data/UPGRADING.md +50 -21
  6. data/docs/README.md +196 -0
  7. data/docs/angles.md +137 -0
  8. data/docs/celestial_bodies.md +107 -0
  9. data/docs/configuration.md +98 -0
  10. data/docs/coordinates.md +167 -0
  11. data/docs/ephem.md +85 -0
  12. data/docs/equinoxes_solstices_times.md +31 -0
  13. data/docs/glossary.md +152 -0
  14. data/docs/instant.md +139 -0
  15. data/docs/moon_phases.md +79 -0
  16. data/docs/observer.md +65 -0
  17. data/docs/reference_frames.md +138 -0
  18. data/docs/rise_transit_set_times.md +119 -0
  19. data/docs/twilight_times.md +123 -0
  20. data/lib/astronoby/bodies/earth.rb +8 -2
  21. data/lib/astronoby/bodies/jupiter.rb +17 -0
  22. data/lib/astronoby/bodies/mars.rb +17 -0
  23. data/lib/astronoby/bodies/mercury.rb +21 -0
  24. data/lib/astronoby/bodies/moon.rb +29 -36
  25. data/lib/astronoby/bodies/neptune.rb +21 -0
  26. data/lib/astronoby/bodies/saturn.rb +26 -0
  27. data/lib/astronoby/bodies/solar_system_body.rb +139 -29
  28. data/lib/astronoby/bodies/sun.rb +25 -2
  29. data/lib/astronoby/bodies/uranus.rb +5 -0
  30. data/lib/astronoby/bodies/venus.rb +25 -0
  31. data/lib/astronoby/cache.rb +188 -0
  32. data/lib/astronoby/configuration.rb +92 -0
  33. data/lib/astronoby/constants.rb +4 -1
  34. data/lib/astronoby/constellation.rb +12 -0
  35. data/lib/astronoby/constellations/data.rb +42 -0
  36. data/lib/astronoby/constellations/finder.rb +35 -0
  37. data/lib/astronoby/constellations/repository.rb +20 -0
  38. data/lib/astronoby/coordinates/equatorial.rb +3 -3
  39. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  40. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  41. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  42. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  43. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  44. data/lib/astronoby/equinox_solstice.rb +2 -2
  45. data/lib/astronoby/events/moon_phases.rb +15 -14
  46. data/lib/astronoby/events/rise_transit_set_calculator.rb +32 -8
  47. data/lib/astronoby/events/twilight_calculator.rb +115 -60
  48. data/lib/astronoby/events/twilight_events.rb +28 -0
  49. data/lib/astronoby/instant.rb +7 -2
  50. data/lib/astronoby/julian_date.rb +78 -0
  51. data/lib/astronoby/mean_obliquity.rb +8 -10
  52. data/lib/astronoby/nutation.rb +11 -3
  53. data/lib/astronoby/observer.rb +1 -1
  54. data/lib/astronoby/precession.rb +48 -38
  55. data/lib/astronoby/reference_frame.rb +2 -1
  56. data/lib/astronoby/reference_frames/apparent.rb +1 -1
  57. data/lib/astronoby/reference_frames/mean_of_date.rb +1 -1
  58. data/lib/astronoby/reference_frames/topocentric.rb +1 -11
  59. data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
  60. data/lib/astronoby/true_obliquity.rb +2 -3
  61. data/lib/astronoby/util/time.rb +1 -1
  62. data/lib/astronoby/version.rb +1 -1
  63. data/lib/astronoby.rb +8 -1
  64. metadata +59 -11
  65. data/Gemfile +0 -5
  66. data/Gemfile.lock +0 -102
  67. data/benchmark/README.md +0 -131
  68. data/benchmark/benchmark.rb +0 -259
  69. data/benchmark/data/imcce.csv.zip +0 -0
  70. data/benchmark/data/sun_calc.csv.zip +0 -0
  71. data/lib/astronoby/epoch.rb +0 -22
@@ -0,0 +1,123 @@
1
+ # Twilight times
2
+
3
+ In astronomy, twilight is a period of time when the Sun is already set but
4
+ some of its light still illuminates the atmosphere, making the sky brighter than
5
+ during full night.
6
+
7
+ We usually define 4 moments when talking about twilight:
8
+ * sunrise/sunset: right when the Sun goes above the horizon or right after it
9
+ goes below the horizon. The Sun's horizon angle is 0°.
10
+ * civil twilight: when the horizon angle is between 0° and -6°. Usually, during
11
+ this time, artificial light is not needed yet.
12
+ * nautical twilight: when the horizon angle is between -6° and -12°. When the
13
+ nautical twilight starts, the difference between the horizon at sea and the
14
+ sky cannot be seen clearly anymore.
15
+ * astronomical twilight: when the horizon angle is between -12° and -18°. Some
16
+ stars can be seen during this time.
17
+
18
+ These moments change every day and depend on the observer's location. They can
19
+ be computed using `Astronoby::TwilightCalculator`.
20
+
21
+ ## Initialization
22
+
23
+ Once instantiated, the calculator doesn't do anything yet, it waits for your
24
+ instruction.
25
+
26
+ It takes as key arguments:
27
+ * `observer` (`Astronoby::Observer`): location on Earth of the observer
28
+ * `ephem`: ephemeris to provide the initial raw data
29
+
30
+ You can learn more about ephemerides on the [Ephem page].
31
+
32
+ ```rb
33
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
34
+
35
+ observer = Astronoby::Observer.new(
36
+ latitude: Astronoby::Angle.from_degrees(41.0082),
37
+ longitude: Astronoby::Angle.from_degrees(28.9784),
38
+ elevation: Astronoby::Distance.from_meters(40)
39
+ )
40
+
41
+ calculator = Astronoby::TwilightCalculator.new(
42
+ observer: observer,
43
+ ephem: ephem
44
+ )
45
+ ```
46
+
47
+ You can learn more about observers on the [Observer page].
48
+
49
+ ## `events_between`
50
+
51
+ This is the main method of the calculator. It provides all the twilight times
52
+ that will happen between two dates.
53
+
54
+ It returns a `Astronoby::TwilightEvents` object which exposes the 6 following
55
+ instance methods:
56
+ * `#morning_astronomical_twilight_times`: when the rising Sun reaches 18° below
57
+ the horizon
58
+ * `#morning_nautical_twilight_times`: when the rising Sun reaches 12° below the
59
+ horizon
60
+ * `#morning_civil_twilight_times`: when the rising Sun reaches 6° below the
61
+ horizon
62
+ * `#evening_civil_twilight_times`: when the setting Sun reaches 6° below the
63
+ horizon
64
+ * `#evening_nautical_twilight_times`: when the setting Sun reaches 12° below the
65
+ horizon
66
+ * `#evening_astronomical_twilight_times`: when the setting Sun reaches 18° below
67
+ the horizon
68
+
69
+ ```rb
70
+ events = calculator.events_between(
71
+ Time.utc(2025, 8, 1),
72
+ Time.utc(2025, 8, 8)
73
+ )
74
+
75
+ events.morning_civil_twilight_times
76
+ # =>
77
+ # [2025-08-01 02:29:17 UTC,
78
+ # 2025-08-02 02:30:21 UTC,
79
+ # 2025-08-03 02:31:26 UTC,
80
+ # 2025-08-04 02:32:30 UTC,
81
+ # 2025-08-05 02:33:35 UTC,
82
+ # 2025-08-06 02:34:40 UTC,
83
+ # 2025-08-07 02:35:45 UTC]
84
+ ```
85
+
86
+ ## `#event_on`
87
+
88
+ The calculator exposes the instance method `#event_on` to compute the twilight
89
+ times for a given `date` (`Date`) parameter.
90
+
91
+ It returns a `Astronoby::TwilightEvent` object which exposes the 6 following
92
+ instance methods: `#morning_astronomical_twilight_time`,
93
+ `#morning_nautical_twilight_time`, `#morning_civil_twilight_time`,
94
+ `#evening_civil_twilight_time`, `#evening_nautical_twilight_time` and
95
+ `#evening_astronomical_twilight_time`.
96
+
97
+ ```rb
98
+ event = calculator.event_on(Date.new(2025, 5, 1))
99
+
100
+ event.morning_astronomical_twilight_time
101
+ # => 2025-05-01 01:17:18 UTC
102
+
103
+ event.morning_nautical_twilight_time
104
+ # => 2025-05-01 01:56:48 UTC
105
+
106
+ event.evening_civil_twilight_time
107
+ # => 2025-05-01 17:29:41 UTC
108
+
109
+ event.evening_nautical_twilight_time
110
+ # => 2025-05-01 18:06:08 UTC
111
+
112
+ event.evening_astronomical_twilight_time
113
+ # => 2025-05-01 18:45:38 UTC
114
+ ```
115
+
116
+ [Ephem page]: ephem.md
117
+ [Observer page]: observer.md
118
+
119
+ ## See also
120
+ - [Rise, Transit and Set Times](rise_transit_set_times.md) - for sun and moon events
121
+ - [Observer](observer.md) - for location setup
122
+ - [Ephemerides](ephem.md) - for data sources
123
+ - [Moon Phases](moon_phases.md) - for lunar events
@@ -17,7 +17,13 @@ module Astronoby
17
17
 
18
18
  private
19
19
 
20
- def compute_astrometric(ephem)
20
+ # Attributes that require Sun data like phase angle or magnitude are not
21
+ # applicable for Earth.
22
+ def requires_sun_data?
23
+ false
24
+ end
25
+
26
+ def compute_astrometric(_ephem)
21
27
  Astrometric.new(
22
28
  position: Vector[
23
29
  Distance.zero,
@@ -35,7 +41,7 @@ module Astronoby
35
41
  )
36
42
  end
37
43
 
38
- def compute_mean_of_date(ephem)
44
+ def compute_mean_of_date(_ephem)
39
45
  MeanOfDate.new(
40
46
  position: Vector[
41
47
  Distance.zero,
@@ -3,9 +3,26 @@
3
3
  module Astronoby
4
4
  class Jupiter < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(71_492_000)
6
+ ABSOLUTE_MAGNITUDE = -9.395
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, JUPITER_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ private
17
+
18
+ # Source:
19
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
20
+ # Almanac (2018)
21
+ # Authors: Anthony Mallama and James L. Hilton
22
+ def magnitude_correction_term
23
+ phase_angle_degrees = phase_angle.degrees
24
+ -3.7 * 10**-4 * phase_angle_degrees +
25
+ 6.16 * 10**-4 * phase_angle_degrees * phase_angle_degrees
26
+ end
10
27
  end
11
28
  end
@@ -3,9 +3,26 @@
3
3
  module Astronoby
4
4
  class Mars < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(3_396_200)
6
+ ABSOLUTE_MAGNITUDE = -1.601
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, MARS_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ private
17
+
18
+ # Source:
19
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
20
+ # Almanac (2018)
21
+ # Authors: Anthony Mallama and James L. Hilton
22
+ def magnitude_correction_term
23
+ phase_angle_degrees = phase_angle.degrees
24
+ 2.267 * 10**-2 * phase_angle_degrees -
25
+ 1.302 * 10**-4 * phase_angle_degrees * phase_angle_degrees
26
+ end
10
27
  end
11
28
  end
@@ -3,9 +3,30 @@
3
3
  module Astronoby
4
4
  class Mercury < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(2_439_700)
6
+ ABSOLUTE_MAGNITUDE = -0.613
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, MERCURY_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ private
17
+
18
+ # Source:
19
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
20
+ # Almanac (2018)
21
+ # Authors: Anthony Mallama and James L. Hilton
22
+ def magnitude_correction_term
23
+ phase_angle_degrees = phase_angle.degrees
24
+ 6.328 * 10**-2 * phase_angle_degrees -
25
+ 1.6336 * 10**-3 * phase_angle_degrees * phase_angle_degrees +
26
+ 3.3634 * 10**-5 * phase_angle_degrees**3 -
27
+ 3.4265 * 10**-7 * phase_angle_degrees**4 +
28
+ 1.6893 * 10**-9 * phase_angle_degrees**5 -
29
+ 3.0334 * 10**-12 * phase_angle_degrees**6
30
+ end
10
31
  end
11
32
  end
@@ -4,6 +4,7 @@ module Astronoby
4
4
  class Moon < SolarSystemBody
5
5
  SEMIDIAMETER_VARIATION = 0.7275
6
6
  EQUATORIAL_RADIUS = Distance.from_meters(1_737_400)
7
+ ABSOLUTE_MAGNITUDE = 0.28
7
8
 
8
9
  def self.ephemeris_segments(ephem_source)
9
10
  if ephem_source == ::Ephem::SPK::JPL_DE
@@ -32,16 +33,8 @@ module Astronoby
32
33
  Events::MoonPhases.phases_for(year: year, month: month)
33
34
  end
34
35
 
35
- attr_reader :phase_angle
36
-
37
- def initialize(instant:, ephem:)
38
- super
39
- @phase_angle = compute_phase_angle(ephem)
40
- end
41
-
42
- # @return [Float] Moon's illuminated fraction
43
- def illuminated_fraction
44
- @illuminated_fraction ||= (1 + phase_angle.cos) / 2.0
36
+ def self.absolute_magnitude
37
+ ABSOLUTE_MAGNITUDE
45
38
  end
46
39
 
47
40
  # @return [Float] Phase fraction, from 0 to 1
@@ -56,31 +49,6 @@ module Astronoby
56
49
  # Author: Jean Meeus
57
50
  # Edition: 2nd edition
58
51
  # Chapter: 48 - Illuminated Fraction of the Moon's Disk
59
-
60
- # @return [Angle] Moon's phase angle
61
- def compute_phase_angle(ephem)
62
- @phase_angle ||= begin
63
- sun = Sun.new(instant: @instant, ephem: ephem)
64
- geocentric_elongation = Angle.acos(
65
- sun.apparent.equatorial.declination.sin *
66
- apparent.equatorial.declination.sin +
67
- sun.apparent.equatorial.declination.cos *
68
- apparent.equatorial.declination.cos *
69
- (
70
- sun.apparent.equatorial.right_ascension -
71
- apparent.equatorial.right_ascension
72
- ).cos
73
- )
74
-
75
- term1 = sun.astrometric.distance.km * geocentric_elongation.sin
76
- term2 = astrometric.distance.km -
77
- sun.astrometric.distance.km * geocentric_elongation.cos
78
- angle = Angle.atan(term1 / term2)
79
- Astronoby::Util::Trigonometry
80
- .adjustement_for_arctangent(term1, term2, angle)
81
- end
82
- end
83
-
84
52
  def mean_elongation
85
53
  @mean_elongation ||= Angle.from_degrees(
86
54
  (
@@ -94,7 +62,32 @@ module Astronoby
94
62
  end
95
63
 
96
64
  def elapsed_centuries
97
- (@instant.tt - Epoch::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
65
+ (@instant.tt - JulianDate::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
66
+ end
67
+
68
+ private
69
+
70
+ # Source:
71
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
72
+ # Almanac (2018)
73
+ # Authors: Anthony Mallama and James L. Hilton
74
+ def magnitude_correction_term
75
+ phase_angle_degrees = phase_angle.degrees
76
+ if phase_angle_degrees <= 150 && current_phase_fraction <= 0.5
77
+ 2.9994 * 10**-2 * phase_angle_degrees -
78
+ 1.6057 * 10**-4 * phase_angle_degrees**2 +
79
+ 3.1543 * 10**-6 * phase_angle_degrees**3 -
80
+ 2.0667 * 10**-8 * phase_angle_degrees**4 +
81
+ 6.2553 * 10**-11 * phase_angle_degrees**5
82
+ elsif phase_angle_degrees <= 150 && current_phase_fraction > 0.5
83
+ 3.3234 * 10**-2 * phase_angle_degrees -
84
+ 3.0725 * 10**-4 * phase_angle_degrees**2 +
85
+ 6.1575 * 10**-6 * phase_angle_degrees**3 -
86
+ 4.7723 * 10**-8 * phase_angle_degrees**4 +
87
+ 1.4681 * 10**-10 * phase_angle_degrees**5
88
+ else
89
+ super
90
+ end
98
91
  end
99
92
  end
100
93
  end
@@ -3,9 +3,30 @@
3
3
  module Astronoby
4
4
  class Neptune < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(24_764_000)
6
+ ABSOLUTE_MAGNITUDE = -7.0
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, NEPTUNE_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ private
17
+
18
+ # Source:
19
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
20
+ # Almanac (2018)
21
+ # Authors: Anthony Mallama and James L. Hilton
22
+ def magnitude_correction_term
23
+ phase_angle_degrees = phase_angle.degrees
24
+ if phase_angle_degrees < 133 && @instant.tt > JulianDate::J2000
25
+ 7.944 * 10**-3 * phase_angle_degrees +
26
+ 9.617 * 10**-5 * phase_angle_degrees * phase_angle_degrees
27
+ else
28
+ super
29
+ end
30
+ end
10
31
  end
11
32
  end
@@ -3,9 +3,35 @@
3
3
  module Astronoby
4
4
  class Saturn < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(60_268_000)
6
+ ABSOLUTE_MAGNITUDE = -8.914
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, SATURN_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ private
17
+
18
+ # Source:
19
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
20
+ # Almanac (2018)
21
+ # Authors: Anthony Mallama and James L. Hilton
22
+ def magnitude_correction_term
23
+ phase_angle_degrees = phase_angle.degrees
24
+ if phase_angle_degrees <= 6
25
+ -0.036 -
26
+ 3.7 * 10**-4 * phase_angle_degrees +
27
+ 6.16 * 10**-4 * phase_angle_degrees * phase_angle_degrees
28
+ else
29
+ 0.026 +
30
+ 2.446 * 10**-4 * phase_angle_degrees +
31
+ 2.672 * 10**-4 * phase_angle_degrees * phase_angle_degrees -
32
+ 1.505 * 10**-6 * phase_angle_degrees**3 +
33
+ 4.767 * 10**-9 * phase_angle_degrees**4
34
+ end
35
+ end
10
36
  end
11
37
  end
@@ -27,42 +27,49 @@ module Astronoby
27
27
  segments = ephemeris_segments(ephem.type)
28
28
  segment1 = segments[0]
29
29
  segment2 = segments[1] if segments.size == 2
30
-
31
- state1 = ephem[*segment1].state_at(instant.terrestrial_time)
32
-
33
- if segment2
34
- state2 = ephem[*segment2].state_at(instant.terrestrial_time)
35
- position = state1.position + state2.position
36
- velocity = state1.velocity + state2.velocity
37
- else
38
- position = state1.position
39
- velocity = state1.velocity
30
+ cache_key = CacheKey.generate(:geometric, instant, segment1, segment2)
31
+
32
+ Astronoby.cache.fetch(cache_key) do
33
+ state1 = ephem[*segment1].state_at(instant.tt)
34
+
35
+ if segment2
36
+ state2 = ephem[*segment2].state_at(instant.tt)
37
+ position = state1.position + state2.position
38
+ velocity = state1.velocity + state2.velocity
39
+ else
40
+ position = state1.position
41
+ velocity = state1.velocity
42
+ end
43
+
44
+ position_vector = Vector[
45
+ Distance.from_kilometers(position.x),
46
+ Distance.from_kilometers(position.y),
47
+ Distance.from_kilometers(position.z)
48
+ ]
49
+
50
+ velocity_vector = Vector[
51
+ Velocity.from_kilometers_per_day(velocity.x),
52
+ Velocity.from_kilometers_per_day(velocity.y),
53
+ Velocity.from_kilometers_per_day(velocity.z)
54
+ ]
55
+
56
+ Geometric.new(
57
+ position: position_vector,
58
+ velocity: velocity_vector,
59
+ instant: instant,
60
+ target_body: self
61
+ )
40
62
  end
41
-
42
- position_vector = Vector[
43
- Distance.from_kilometers(position.x),
44
- Distance.from_kilometers(position.y),
45
- Distance.from_kilometers(position.z)
46
- ]
47
-
48
- velocity_vector = Vector[
49
- Velocity.from_kilometers_per_day(velocity.x),
50
- Velocity.from_kilometers_per_day(velocity.y),
51
- Velocity.from_kilometers_per_day(velocity.z)
52
- ]
53
-
54
- Geometric.new(
55
- position: position_vector,
56
- velocity: velocity_vector,
57
- instant: instant,
58
- target_body: self
59
- )
60
63
  end
61
64
 
62
65
  def self.ephemeris_segments(_ephem_source)
63
66
  raise NotImplementedError
64
67
  end
65
68
 
69
+ def self.absolute_magnitude
70
+ nil
71
+ end
72
+
66
73
  def initialize(ephem:, instant:)
67
74
  @instant = instant
68
75
  @geometric = compute_geometric(ephem)
@@ -74,6 +81,7 @@ module Astronoby
74
81
  target: @geometric,
75
82
  ephem: ephem
76
83
  )
84
+ compute_sun(ephem) if requires_sun_data?
77
85
  end
78
86
 
79
87
  def astrometric
@@ -113,10 +121,112 @@ module Astronoby
113
121
  )
114
122
  end
115
123
 
124
+ # Returns the constellation of the body
125
+ # @return [Astronoby::Constellation, nil]
126
+ def constellation
127
+ @constellation ||= Constellations::Finder.find(
128
+ Precession.for_equatorial_coordinates(
129
+ coordinates: astrometric.equatorial,
130
+ epoch: JulianDate::B1875
131
+ )
132
+ )
133
+ end
134
+
135
+ # Source:
136
+ # Title: Astronomical Algorithms
137
+ # Author: Jean Meeus
138
+ # Edition: 2nd edition
139
+ # Chapter: 48 - Illuminated Fraction of the Moon's Disk
140
+ # @return [Astronoby::Angle, nil] Phase angle of the body
141
+ def phase_angle
142
+ return unless @sun
143
+
144
+ @phase_angle ||= begin
145
+ geocentric_elongation = Angle.acos(
146
+ @sun.apparent.equatorial.declination.sin *
147
+ apparent.equatorial.declination.sin +
148
+ @sun.apparent.equatorial.declination.cos *
149
+ apparent.equatorial.declination.cos *
150
+ (
151
+ @sun.apparent.equatorial.right_ascension -
152
+ apparent.equatorial.right_ascension
153
+ ).cos
154
+ )
155
+
156
+ term1 = @sun.astrometric.distance.km * geocentric_elongation.sin
157
+ term2 = astrometric.distance.km -
158
+ @sun.astrometric.distance.km * geocentric_elongation.cos
159
+ angle = Angle.atan(term1 / term2)
160
+ Astronoby::Util::Trigonometry
161
+ .adjustement_for_arctangent(term1, term2, angle)
162
+ end
163
+ end
164
+
165
+ # Fraction between 0 and 1 of the body's disk that is illuminated.
166
+ # @return [Float, nil] Body's illuminated fraction, between 0 and 1.
167
+ def illuminated_fraction
168
+ return unless phase_angle
169
+
170
+ @illuminated_fraction ||= (1 + phase_angle.cos) / 2.0
171
+ end
172
+
173
+ # Source:
174
+ # Title: Astronomical Algorithms
175
+ # Author: Jean Meeus
176
+ # Edition: 2nd edition
177
+ # Chapter: 48 - Illuminated Fraction of the Moon's Disk
178
+ # Apparent magnitude of the body, as seen from Earth.
179
+ # @return [Float, nil] Apparent magnitude of the body.
180
+ def apparent_magnitude
181
+ return unless self.class.absolute_magnitude
182
+
183
+ @apparent_magnitude ||= begin
184
+ body_sun_distance =
185
+ (astrometric.position - @sun.astrometric.position).magnitude
186
+ self.class.absolute_magnitude +
187
+ 5 * Math.log10(body_sun_distance.au * astrometric.distance.au) +
188
+ magnitude_correction_term
189
+ end
190
+ end
191
+
192
+ # Angular diameter of the body, as seen from Earth. Based on the apparent
193
+ # position of the body.
194
+ # @return [Astronoby::Angle] Angular diameter of the body
195
+ def angular_diameter
196
+ @angular_radius ||= begin
197
+ return if apparent.position.zero?
198
+
199
+ Angle.from_radians(
200
+ Math.asin(self.class::EQUATORIAL_RADIUS.m / apparent.distance.m) * 2
201
+ )
202
+ end
203
+ end
204
+
116
205
  private
117
206
 
207
+ # By default, Solar System bodies expose attributes that are dependent on
208
+ # the Sun's position, such as phase angle and illuminated fraction.
209
+ # If a body does not require Sun data, it should override this method to
210
+ # return false.
211
+ def requires_sun_data?
212
+ true
213
+ end
214
+
118
215
  def compute_geometric(ephem)
119
216
  self.class.compute_geometric(ephem: ephem, instant: @instant)
120
217
  end
218
+
219
+ def compute_sun(ephem)
220
+ @sun ||= Sun.new(instant: @instant, ephem: ephem)
221
+ end
222
+
223
+ # Source:
224
+ # Title: Explanatory Supplement to the Astronomical Almanac
225
+ # Authors: Sean E. Urban and P. Kenneth Seidelmann
226
+ # Edition: University Science Books
227
+ # Chapter: 10.3 - Phases and Magnitudes
228
+ def magnitude_correction_term
229
+ -2.5 * Math.log10(illuminated_fraction)
230
+ end
121
231
  end
122
232
  end
@@ -3,11 +3,28 @@
3
3
  module Astronoby
4
4
  class Sun < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(695_700_000)
6
+ ABSOLUTE_MAGNITUDE = -26.74
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, SUN]]
9
10
  end
10
11
 
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ # Source:
17
+ # Title: Explanatory Supplement to the Astronomical Almanac
18
+ # Authors: Sean E. Urban and P. Kenneth Seidelmann
19
+ # Edition: University Science Books
20
+ # Chapter: 10.3 - Phases and Magnitudes
21
+ # Apparent magnitude of the body, as seen from Earth.
22
+ # @return [Float] Apparent magnitude of the body.
23
+ def apparent_magnitude
24
+ @apparent_magnitude ||=
25
+ self.class.absolute_magnitude + 5 * Math.log10(astrometric.distance.au)
26
+ end
27
+
11
28
  # Source:
12
29
  # Title: Astronomical Algorithms
13
30
  # Author: Jean Meeus
@@ -17,7 +34,7 @@ module Astronoby
17
34
  # @return [Integer] Equation of time in seconds
18
35
  def equation_of_time
19
36
  right_ascension = apparent.equatorial.right_ascension
20
- t = (@instant.julian_date - Epoch::J2000) / Constants::DAYS_PER_JULIAN_MILLENIA
37
+ t = (@instant.julian_date - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_MILLENIA
21
38
  l0 = (280.4664567 +
22
39
  360_007.6982779 * t +
23
40
  0.03032028 * t**2 +
@@ -25,7 +42,7 @@ module Astronoby
25
42
  t**4 / 15_300 -
26
43
  t**5 / 2_000_000) % Constants::DEGREES_PER_CIRCLE
27
44
  nutation = Nutation.new(instant: instant).nutation_in_longitude
28
- obliquity = TrueObliquity.for_epoch(@instant.julian_date)
45
+ obliquity = TrueObliquity.at(@instant)
29
46
 
30
47
  (
31
48
  Angle
@@ -37,5 +54,11 @@ module Astronoby
37
54
  ).hours * Constants::SECONDS_PER_HOUR
38
55
  ).round
39
56
  end
57
+
58
+ private
59
+
60
+ def requires_sun_data?
61
+ false
62
+ end
40
63
  end
41
64
  end
@@ -3,9 +3,14 @@
3
3
  module Astronoby
4
4
  class Uranus < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(25_559_000)
6
+ ABSOLUTE_MAGNITUDE = -7.11
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, URANUS_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
10
15
  end
11
16
  end
@@ -3,9 +3,34 @@
3
3
  module Astronoby
4
4
  class Venus < SolarSystemBody
5
5
  EQUATORIAL_RADIUS = Distance.from_meters(6_051_800)
6
+ ABSOLUTE_MAGNITUDE = -4.384
6
7
 
7
8
  def self.ephemeris_segments(_ephem_source)
8
9
  [[SOLAR_SYSTEM_BARYCENTER, VENUS_BARYCENTER]]
9
10
  end
11
+
12
+ def self.absolute_magnitude
13
+ ABSOLUTE_MAGNITUDE
14
+ end
15
+
16
+ private
17
+
18
+ # Source:
19
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
20
+ # Almanac (2018)
21
+ # Authors: Anthony Mallama and James L. Hilton
22
+ def magnitude_correction_term
23
+ phase_angle_degrees = phase_angle.degrees
24
+ if phase_angle_degrees < 163.7
25
+ -1.044 * 10**-3 * phase_angle_degrees +
26
+ 3.687 * 10**-4 * phase_angle_degrees * phase_angle_degrees -
27
+ 2.814 * 10**-6 * phase_angle_degrees**3 +
28
+ 8.938 * 10**-9 * phase_angle_degrees**4
29
+
30
+ else
31
+ 240.44228 - 2.81914 * phase_angle_degrees +
32
+ 8.39034 * 10**-3 * phase_angle_degrees * phase_angle_degrees
33
+ end
34
+ end
10
35
  end
11
36
  end