astronoby 0.7.0 → 0.9.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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +145 -3
  4. data/README.md +59 -33
  5. data/UPGRADING.md +75 -21
  6. data/docs/README.md +224 -0
  7. data/docs/angles.md +137 -0
  8. data/docs/configuration.md +98 -0
  9. data/docs/coordinates.md +167 -0
  10. data/docs/deep_sky_bodies.md +101 -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/solar_system_bodies.md +107 -0
  20. data/docs/twilight_times.md +123 -0
  21. data/lib/astronoby/angle.rb +6 -2
  22. data/lib/astronoby/angular_velocity.rb +76 -0
  23. data/lib/astronoby/bodies/deep_sky_object.rb +44 -0
  24. data/lib/astronoby/bodies/deep_sky_object_position.rb +127 -0
  25. data/lib/astronoby/bodies/earth.rb +12 -2
  26. data/lib/astronoby/bodies/jupiter.rb +17 -0
  27. data/lib/astronoby/bodies/mars.rb +17 -0
  28. data/lib/astronoby/bodies/mercury.rb +21 -0
  29. data/lib/astronoby/bodies/moon.rb +50 -36
  30. data/lib/astronoby/bodies/neptune.rb +21 -0
  31. data/lib/astronoby/bodies/saturn.rb +26 -0
  32. data/lib/astronoby/bodies/solar_system_body.rb +162 -27
  33. data/lib/astronoby/bodies/sun.rb +25 -2
  34. data/lib/astronoby/bodies/uranus.rb +5 -0
  35. data/lib/astronoby/bodies/venus.rb +25 -0
  36. data/lib/astronoby/cache.rb +189 -0
  37. data/lib/astronoby/configuration.rb +92 -0
  38. data/lib/astronoby/constants.rb +11 -3
  39. data/lib/astronoby/constellation.rb +12 -0
  40. data/lib/astronoby/constellations/data.rb +42 -0
  41. data/lib/astronoby/constellations/finder.rb +35 -0
  42. data/lib/astronoby/constellations/repository.rb +20 -0
  43. data/lib/astronoby/coordinates/equatorial.rb +5 -8
  44. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  45. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  46. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  47. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  48. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  49. data/lib/astronoby/distance.rb +6 -0
  50. data/lib/astronoby/equinox_solstice.rb +2 -2
  51. data/lib/astronoby/events/extremum_calculator.rb +233 -0
  52. data/lib/astronoby/events/extremum_event.rb +15 -0
  53. data/lib/astronoby/events/moon_phases.rb +15 -14
  54. data/lib/astronoby/events/rise_transit_set_calculator.rb +39 -12
  55. data/lib/astronoby/events/twilight_calculator.rb +116 -61
  56. data/lib/astronoby/events/twilight_events.rb +28 -0
  57. data/lib/astronoby/instant.rb +34 -6
  58. data/lib/astronoby/julian_date.rb +78 -0
  59. data/lib/astronoby/mean_obliquity.rb +8 -10
  60. data/lib/astronoby/nutation.rb +11 -3
  61. data/lib/astronoby/observer.rb +1 -1
  62. data/lib/astronoby/precession.rb +48 -38
  63. data/lib/astronoby/reference_frame.rb +2 -1
  64. data/lib/astronoby/reference_frames/apparent.rb +1 -11
  65. data/lib/astronoby/reference_frames/mean_of_date.rb +1 -1
  66. data/lib/astronoby/reference_frames/topocentric.rb +2 -12
  67. data/lib/astronoby/stellar_propagation.rb +162 -0
  68. data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +22 -0
  69. data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +64 -0
  70. data/lib/astronoby/time/greenwich_sidereal_time.rb +20 -58
  71. data/lib/astronoby/time/local_apparent_sidereal_time.rb +42 -0
  72. data/lib/astronoby/time/local_mean_sidereal_time.rb +42 -0
  73. data/lib/astronoby/time/local_sidereal_time.rb +35 -26
  74. data/lib/astronoby/time/sidereal_time.rb +42 -0
  75. data/lib/astronoby/true_obliquity.rb +2 -3
  76. data/lib/astronoby/util/time.rb +62 -44
  77. data/lib/astronoby/velocity.rb +5 -0
  78. data/lib/astronoby/version.rb +1 -1
  79. data/lib/astronoby.rb +19 -1
  80. metadata +71 -11
  81. data/Gemfile +0 -5
  82. data/Gemfile.lock +0 -102
  83. data/benchmark/README.md +0 -131
  84. data/benchmark/benchmark.rb +0 -259
  85. data/benchmark/data/imcce.csv.zip +0 -0
  86. data/benchmark/data/sun_calc.csv.zip +0 -0
  87. data/lib/astronoby/epoch.rb +0 -22
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class DeepSkyObjectPosition
5
+ DEFAULT_DISTANCE = Distance.from_parsecs(1e9)
6
+
7
+ attr_reader :instant, :apparent
8
+
9
+ # @param instant [Astronoby::Instant] Instant of the observation
10
+ # @param equatorial_coordinates [Astronoby::Coordinates::Equatorial]
11
+ # Equatorial coordinates at epoch J2000.0
12
+ # @param proper_motion_ra [Astronoby::AngularVelocity, nil] Proper motion in
13
+ # right ascension
14
+ # @param proper_motion_dec [Astronoby::AngularVelocity, nil] Proper motion
15
+ # in declination
16
+ # @param parallax [Astronoby::Angle, nil] Parallax angle
17
+ # @param radial_velocity [Astronoby::Velocity, nil] Radial velocity
18
+ def initialize(
19
+ instant:,
20
+ equatorial_coordinates:,
21
+ ephem: nil,
22
+ proper_motion_ra: nil,
23
+ proper_motion_dec: nil,
24
+ parallax: nil,
25
+ radial_velocity: nil
26
+ )
27
+ @instant = instant
28
+ @initial_equatorial_coordinates = equatorial_coordinates
29
+ @proper_motion_ra = proper_motion_ra
30
+ @proper_motion_dec = proper_motion_dec
31
+ @parallax = parallax
32
+ @radial_velocity = radial_velocity
33
+ if ephem
34
+ @earth_geometric = Earth.geometric(ephem: ephem, instant: @instant)
35
+ end
36
+ compute_apparent
37
+ end
38
+
39
+ # @return [Astronoby::Astrometric] Astrometric position of the object
40
+ def astrometric
41
+ @astrometric ||= Astrometric.new(
42
+ instant: @instant,
43
+ position: astrometric_position,
44
+ velocity: astrometric_velocity,
45
+ center_identifier: SolarSystemBody::EARTH,
46
+ target_body: self
47
+ )
48
+ end
49
+
50
+ def observed_by(observer)
51
+ Topocentric.build_from_apparent(
52
+ apparent: @apparent,
53
+ observer: observer,
54
+ instant: @instant,
55
+ target_body: self
56
+ )
57
+ end
58
+
59
+ private
60
+
61
+ def astrometric_position
62
+ @astrometric_position ||= if use_stellar_propagation?
63
+ stellar_propagation.position
64
+ else
65
+ astronomical_distance = DEFAULT_DISTANCE.meters
66
+ right_ascension = @initial_equatorial_coordinates.right_ascension
67
+ declination = @initial_equatorial_coordinates.declination
68
+ Distance.vector_from_meters([
69
+ declination.cos * right_ascension.cos * astronomical_distance,
70
+ declination.cos * right_ascension.sin * astronomical_distance,
71
+ declination.sin * astronomical_distance
72
+ ])
73
+ end
74
+ end
75
+
76
+ def astrometric_velocity
77
+ @astrometric_velocity ||= if use_stellar_propagation?
78
+ stellar_propagation.velocity_vector
79
+ else
80
+ Velocity.vector_from_meters_per_second([0.0, 0.0, 0.0])
81
+ end
82
+ end
83
+
84
+ def use_stellar_propagation?
85
+ @proper_motion_ra && @proper_motion_dec && @parallax && @radial_velocity
86
+ end
87
+
88
+ def stellar_propagation
89
+ @stellar_propagation ||= StellarPropagation.new(
90
+ equatorial_coordinates: @initial_equatorial_coordinates,
91
+ proper_motion_ra: @proper_motion_ra,
92
+ proper_motion_dec: @proper_motion_dec,
93
+ parallax: @parallax,
94
+ radial_velocity: @radial_velocity,
95
+ instant: @instant,
96
+ earth_geometric: @earth_geometric
97
+ )
98
+ end
99
+
100
+ def compute_apparent
101
+ @apparent = if @earth_geometric
102
+ Apparent.build_from_astrometric(
103
+ instant: @instant,
104
+ target_astrometric: astrometric,
105
+ earth_geometric: @earth_geometric,
106
+ target_body: self
107
+ )
108
+ else
109
+ precession_matrix = Precession.matrix_for(@instant)
110
+ nutation_matrix = Nutation.matrix_for(@instant)
111
+ corrected_position = Distance.vector_from_meters(
112
+ precession_matrix * nutation_matrix * astrometric.position.map(&:m)
113
+ )
114
+ corrected_velocity = Velocity.vector_from_mps(
115
+ precession_matrix * nutation_matrix * astrometric.velocity.map(&:mps)
116
+ )
117
+ Apparent.new(
118
+ position: corrected_position,
119
+ velocity: corrected_velocity,
120
+ instant: @instant,
121
+ center_identifier: SolarSystemBody::EARTH,
122
+ target_body: self
123
+ )
124
+ end
125
+ end
126
+ end
127
+ end
@@ -15,9 +15,19 @@ module Astronoby
15
15
  end
16
16
  end
17
17
 
18
+ def phase_angle
19
+ nil
20
+ end
21
+
18
22
  private
19
23
 
20
- def compute_astrometric(ephem)
24
+ # Attributes that require Sun data like phase angle or magnitude are not
25
+ # applicable for Earth.
26
+ def requires_sun_data?
27
+ true
28
+ end
29
+
30
+ def compute_astrometric(_ephem)
21
31
  Astrometric.new(
22
32
  position: Vector[
23
33
  Distance.zero,
@@ -35,7 +45,7 @@ module Astronoby
35
45
  )
36
46
  end
37
47
 
38
- def compute_mean_of_date(ephem)
48
+ def compute_mean_of_date(_ephem)
39
49
  MeanOfDate.new(
40
50
  position: Vector[
41
51
  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
@@ -49,6 +42,27 @@ module Astronoby
49
42
  mean_elongation.degrees / Constants::DEGREES_PER_CIRCLE
50
43
  end
51
44
 
45
+ # @return [Boolean] True if the body is approaching the primary
46
+ # body (Earth), false otherwise.
47
+ def approaching_primary?
48
+ relative_position =
49
+ (geometric.position - @earth_geometric.position).map(&:m)
50
+ relative_velocity =
51
+ (geometric.velocity - @earth_geometric.velocity).map(&:mps)
52
+ radial_velocity_component = Astronoby::Util::Maths
53
+ .dot_product(relative_position, relative_velocity)
54
+ distance = Math.sqrt(
55
+ Astronoby::Util::Maths.dot_product(relative_position, relative_position)
56
+ )
57
+ radial_velocity_component / distance < 0
58
+ end
59
+
60
+ # @return [Boolean] True if the body is receding from the primary
61
+ # body (Earth), false otherwise.
62
+ def receding_from_primary?
63
+ !approaching_primary?
64
+ end
65
+
52
66
  private
53
67
 
54
68
  # Source:
@@ -56,31 +70,6 @@ module Astronoby
56
70
  # Author: Jean Meeus
57
71
  # Edition: 2nd edition
58
72
  # 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
73
  def mean_elongation
85
74
  @mean_elongation ||= Angle.from_degrees(
86
75
  (
@@ -94,7 +83,32 @@ module Astronoby
94
83
  end
95
84
 
96
85
  def elapsed_centuries
97
- (@instant.tt - Epoch::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
86
+ (@instant.tt - JulianDate::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
87
+ end
88
+
89
+ private
90
+
91
+ # Source:
92
+ # Title: Computing Apparent Planetary Magnitudes for The Astronomical
93
+ # Almanac (2018)
94
+ # Authors: Anthony Mallama and James L. Hilton
95
+ def magnitude_correction_term
96
+ phase_angle_degrees = phase_angle.degrees
97
+ if phase_angle_degrees <= 150 && current_phase_fraction <= 0.5
98
+ 2.9994 * 10**-2 * phase_angle_degrees -
99
+ 1.6057 * 10**-4 * phase_angle_degrees**2 +
100
+ 3.1543 * 10**-6 * phase_angle_degrees**3 -
101
+ 2.0667 * 10**-8 * phase_angle_degrees**4 +
102
+ 6.2553 * 10**-11 * phase_angle_degrees**5
103
+ elsif phase_angle_degrees <= 150 && current_phase_fraction > 0.5
104
+ 3.3234 * 10**-2 * phase_angle_degrees -
105
+ 3.0725 * 10**-4 * phase_angle_degrees**2 +
106
+ 6.1575 * 10**-6 * phase_angle_degrees**3 -
107
+ 4.7723 * 10**-8 * phase_angle_degrees**4 +
108
+ 1.4681 * 10**-10 * phase_angle_degrees**5
109
+ else
110
+ super
111
+ end
98
112
  end
99
113
  end
100
114
  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
@@ -19,6 +19,10 @@ module Astronoby
19
19
 
20
20
  attr_reader :geometric, :instant
21
21
 
22
+ def self.at(instant, ephem:)
23
+ new(ephem: ephem, instant: instant)
24
+ end
25
+
22
26
  def self.geometric(ephem:, instant:)
23
27
  compute_geometric(ephem: ephem, instant: instant)
24
28
  end
@@ -27,42 +31,49 @@ module Astronoby
27
31
  segments = ephemeris_segments(ephem.type)
28
32
  segment1 = segments[0]
29
33
  segment2 = segments[1] if segments.size == 2
34
+ cache_key = CacheKey.generate(:geometric, instant, segment1, segment2)
30
35
 
31
- state1 = ephem[*segment1].state_at(instant.terrestrial_time)
36
+ Astronoby.cache.fetch(cache_key) do
37
+ state1 = ephem[*segment1].state_at(instant.tt)
32
38
 
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
40
- end
39
+ if segment2
40
+ state2 = ephem[*segment2].state_at(instant.tt)
41
+ position = state1.position + state2.position
42
+ velocity = state1.velocity + state2.velocity
43
+ else
44
+ position = state1.position
45
+ velocity = state1.velocity
46
+ end
41
47
 
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
- )
48
+ position_vector = Vector[
49
+ Distance.from_kilometers(position.x),
50
+ Distance.from_kilometers(position.y),
51
+ Distance.from_kilometers(position.z)
52
+ ]
53
+
54
+ velocity_vector = Vector[
55
+ Velocity.from_kilometers_per_day(velocity.x),
56
+ Velocity.from_kilometers_per_day(velocity.y),
57
+ Velocity.from_kilometers_per_day(velocity.z)
58
+ ]
59
+
60
+ Geometric.new(
61
+ position: position_vector,
62
+ velocity: velocity_vector,
63
+ instant: instant,
64
+ target_body: self
65
+ )
66
+ end
60
67
  end
61
68
 
62
69
  def self.ephemeris_segments(_ephem_source)
63
70
  raise NotImplementedError
64
71
  end
65
72
 
73
+ def self.absolute_magnitude
74
+ nil
75
+ end
76
+
66
77
  def initialize(ephem:, instant:)
67
78
  @instant = instant
68
79
  @geometric = compute_geometric(ephem)
@@ -74,6 +85,7 @@ module Astronoby
74
85
  target: @geometric,
75
86
  ephem: ephem
76
87
  )
88
+ compute_sun(ephem) if requires_sun_data?
77
89
  end
78
90
 
79
91
  def astrometric
@@ -113,10 +125,133 @@ module Astronoby
113
125
  )
114
126
  end
115
127
 
128
+ # Returns the constellation of the body
129
+ # @return [Astronoby::Constellation, nil]
130
+ def constellation
131
+ @constellation ||= Constellations::Finder.find(
132
+ Precession.for_equatorial_coordinates(
133
+ coordinates: astrometric.equatorial,
134
+ epoch: JulianDate::B1875
135
+ )
136
+ )
137
+ end
138
+
139
+ # Source:
140
+ # Title: Astronomical Algorithms
141
+ # Author: Jean Meeus
142
+ # Edition: 2nd edition
143
+ # Chapter: 48 - Illuminated Fraction of the Moon's Disk
144
+ # @return [Astronoby::Angle, nil] Phase angle of the body
145
+ def phase_angle
146
+ return unless @sun
147
+
148
+ @phase_angle ||= begin
149
+ geocentric_elongation = Angle.acos(
150
+ @sun.apparent.equatorial.declination.sin *
151
+ apparent.equatorial.declination.sin +
152
+ @sun.apparent.equatorial.declination.cos *
153
+ apparent.equatorial.declination.cos *
154
+ (
155
+ @sun.apparent.equatorial.right_ascension -
156
+ apparent.equatorial.right_ascension
157
+ ).cos
158
+ )
159
+
160
+ term1 = @sun.astrometric.distance.km * geocentric_elongation.sin
161
+ term2 = astrometric.distance.km -
162
+ @sun.astrometric.distance.km * geocentric_elongation.cos
163
+ angle = Angle.atan(term1 / term2)
164
+ Astronoby::Util::Trigonometry
165
+ .adjustement_for_arctangent(term1, term2, angle)
166
+ end
167
+ end
168
+
169
+ # Fraction between 0 and 1 of the body's disk that is illuminated.
170
+ # @return [Float, nil] Body's illuminated fraction, between 0 and 1.
171
+ def illuminated_fraction
172
+ return unless phase_angle
173
+
174
+ @illuminated_fraction ||= (1 + phase_angle.cos) / 2.0
175
+ end
176
+
177
+ # Source:
178
+ # Title: Astronomical Algorithms
179
+ # Author: Jean Meeus
180
+ # Edition: 2nd edition
181
+ # Chapter: 48 - Illuminated Fraction of the Moon's Disk
182
+ # Apparent magnitude of the body, as seen from Earth.
183
+ # @return [Float, nil] Apparent magnitude of the body.
184
+ def apparent_magnitude
185
+ return unless self.class.absolute_magnitude
186
+
187
+ @apparent_magnitude ||= begin
188
+ body_sun_distance =
189
+ (astrometric.position - @sun.astrometric.position).magnitude
190
+ self.class.absolute_magnitude +
191
+ 5 * Math.log10(body_sun_distance.au * astrometric.distance.au) +
192
+ magnitude_correction_term
193
+ end
194
+ end
195
+
196
+ # Angular diameter of the body, as seen from Earth. Based on the apparent
197
+ # position of the body.
198
+ # @return [Astronoby::Angle] Angular diameter of the body
199
+ def angular_diameter
200
+ @angular_radius ||= begin
201
+ return if apparent.position.zero?
202
+
203
+ Angle.from_radians(
204
+ Math.asin(self.class::EQUATORIAL_RADIUS.m / apparent.distance.m) * 2
205
+ )
206
+ end
207
+ end
208
+
209
+ # @return [Boolean] True if the body is approaching the primary
210
+ # body (Sun), false otherwise.
211
+ def approaching_primary?
212
+ relative_position =
213
+ (geometric.position - @sun.geometric.position).map(&:m)
214
+ relative_velocity =
215
+ (geometric.velocity - @sun.geometric.velocity).map(&:mps)
216
+ radial_velocity_component = Astronoby::Util::Maths
217
+ .dot_product(relative_position, relative_velocity)
218
+ distance = Math.sqrt(
219
+ Astronoby::Util::Maths.dot_product(relative_position, relative_position)
220
+ )
221
+ radial_velocity_component / distance < 0
222
+ end
223
+
224
+ # @return [Boolean] True if the body is receding from the primary
225
+ # body (Sun), false otherwise.
226
+ def receding_from_primary?
227
+ !approaching_primary?
228
+ end
229
+
116
230
  private
117
231
 
232
+ # By default, Solar System bodies expose attributes that are dependent on
233
+ # the Sun's position, such as phase angle and illuminated fraction.
234
+ # If a body does not require Sun data, it should override this method to
235
+ # return false.
236
+ def requires_sun_data?
237
+ true
238
+ end
239
+
118
240
  def compute_geometric(ephem)
119
241
  self.class.compute_geometric(ephem: ephem, instant: @instant)
120
242
  end
243
+
244
+ def compute_sun(ephem)
245
+ @sun ||= Sun.new(instant: @instant, ephem: ephem)
246
+ end
247
+
248
+ # Source:
249
+ # Title: Explanatory Supplement to the Astronomical Almanac
250
+ # Authors: Sean E. Urban and P. Kenneth Seidelmann
251
+ # Edition: University Science Books
252
+ # Chapter: 10.3 - Phases and Magnitudes
253
+ def magnitude_correction_term
254
+ -2.5 * Math.log10(illuminated_fraction)
255
+ end
121
256
  end
122
257
  end