astronoby 0.6.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +203 -3
  5. data/README.md +69 -288
  6. data/UPGRADING.md +267 -0
  7. data/docs/README.md +196 -0
  8. data/docs/angles.md +137 -0
  9. data/docs/celestial_bodies.md +107 -0
  10. data/docs/configuration.md +98 -0
  11. data/docs/coordinates.md +167 -0
  12. data/docs/ephem.md +85 -0
  13. data/docs/equinoxes_solstices_times.md +31 -0
  14. data/docs/glossary.md +152 -0
  15. data/docs/instant.md +139 -0
  16. data/docs/moon_phases.md +79 -0
  17. data/docs/observer.md +65 -0
  18. data/docs/reference_frames.md +138 -0
  19. data/docs/rise_transit_set_times.md +119 -0
  20. data/docs/twilight_times.md +123 -0
  21. data/lib/astronoby/aberration.rb +56 -31
  22. data/lib/astronoby/angle.rb +20 -16
  23. data/lib/astronoby/angles/dms.rb +2 -2
  24. data/lib/astronoby/angles/hms.rb +2 -2
  25. data/lib/astronoby/bodies/earth.rb +62 -0
  26. data/lib/astronoby/bodies/jupiter.rb +28 -0
  27. data/lib/astronoby/bodies/mars.rb +28 -0
  28. data/lib/astronoby/bodies/mercury.rb +32 -0
  29. data/lib/astronoby/bodies/moon.rb +51 -298
  30. data/lib/astronoby/bodies/neptune.rb +32 -0
  31. data/lib/astronoby/bodies/saturn.rb +37 -0
  32. data/lib/astronoby/bodies/solar_system_body.rb +232 -0
  33. data/lib/astronoby/bodies/sun.rb +33 -214
  34. data/lib/astronoby/bodies/uranus.rb +16 -0
  35. data/lib/astronoby/bodies/venus.rb +36 -0
  36. data/lib/astronoby/cache.rb +188 -0
  37. data/lib/astronoby/configuration.rb +92 -0
  38. data/lib/astronoby/constants.rb +17 -2
  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/ecliptic.rb +2 -37
  44. data/lib/astronoby/coordinates/equatorial.rb +28 -10
  45. data/lib/astronoby/coordinates/horizontal.rb +0 -46
  46. data/lib/astronoby/corrections/light_time_delay.rb +90 -0
  47. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  48. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  49. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  50. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  51. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  52. data/lib/astronoby/deflection.rb +187 -0
  53. data/lib/astronoby/distance.rb +9 -0
  54. data/lib/astronoby/ephem.rb +39 -0
  55. data/lib/astronoby/equinox_solstice.rb +22 -19
  56. data/lib/astronoby/errors.rb +4 -0
  57. data/lib/astronoby/events/moon_phases.rb +15 -13
  58. data/lib/astronoby/events/rise_transit_set_calculator.rb +376 -0
  59. data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
  60. data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
  61. data/lib/astronoby/events/twilight_calculator.rb +221 -0
  62. data/lib/astronoby/events/twilight_event.rb +28 -0
  63. data/lib/astronoby/events/twilight_events.rb +22 -115
  64. data/lib/astronoby/instant.rb +176 -0
  65. data/lib/astronoby/julian_date.rb +78 -0
  66. data/lib/astronoby/mean_obliquity.rb +24 -13
  67. data/lib/astronoby/nutation.rb +235 -42
  68. data/lib/astronoby/observer.rb +55 -0
  69. data/lib/astronoby/precession.rb +102 -18
  70. data/lib/astronoby/reference_frame.rb +50 -0
  71. data/lib/astronoby/reference_frames/apparent.rb +60 -0
  72. data/lib/astronoby/reference_frames/astrometric.rb +21 -0
  73. data/lib/astronoby/reference_frames/geometric.rb +20 -0
  74. data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
  75. data/lib/astronoby/reference_frames/topocentric.rb +72 -0
  76. data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
  77. data/lib/astronoby/true_obliquity.rb +3 -3
  78. data/lib/astronoby/util/maths.rb +70 -73
  79. data/lib/astronoby/util/time.rb +455 -32
  80. data/lib/astronoby/vector.rb +36 -0
  81. data/lib/astronoby/velocity.rb +116 -0
  82. data/lib/astronoby/version.rb +1 -1
  83. data/lib/astronoby.rb +33 -5
  84. metadata +117 -24
  85. data/.tool-versions +0 -1
  86. data/Gemfile +0 -5
  87. data/Gemfile.lock +0 -80
  88. data/benchmark/README.md +0 -131
  89. data/benchmark/benchmark.rb +0 -259
  90. data/benchmark/data/imcce.csv.zip +0 -0
  91. data/benchmark/data/sun_calc.csv.zip +0 -0
  92. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
  93. data/lib/astronoby/epoch.rb +0 -22
  94. data/lib/astronoby/events/observation_events.rb +0 -285
  95. data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -218
  96. data/lib/astronoby/util/astrodynamics.rb +0 -60
@@ -0,0 +1,79 @@
1
+ # Moon Phases
2
+
3
+ Astronoby lets you compute the current Moon phase, or when the major ones
4
+ happen.
5
+
6
+ ## Current Moon phase
7
+
8
+ `Astronoby::Moon` provides two pieces of information about the current Moon phase: the
9
+ illuminated fraction and the phase fraction.
10
+
11
+ ### `#illuminated_fraction`
12
+
13
+ As mentioned in the name, this method provides the illuminated fraction of the
14
+ Moon. It will not give precise information about the "age" of the Moon as the
15
+ same illumination happens twice in the same lunar month.
16
+
17
+ ```rb
18
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
19
+ time = Time.utc(2025, 5, 1)
20
+ instant = Astronoby::Instant.from_time(time)
21
+
22
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
23
+
24
+ moon.illuminated_fraction.round(2)
25
+ # => 0.15
26
+ # 15% of the Moon is illuminated as seen from Earth
27
+ ```
28
+
29
+ ### `#current_phase_fraction`
30
+
31
+ This method is more convenient for a user interested in how far we are into the
32
+ lunar month as it returns a fraction from 0 to 1 between two new Moons.
33
+
34
+ ```rb
35
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
36
+
37
+ time = Time.utc(2025, 5, 1)
38
+ instant = Astronoby::Instant.from_time(time)
39
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
40
+
41
+ moon.current_phase_fraction.round(2)
42
+ # => 0.11
43
+
44
+ time = Time.utc(2025, 5, 15)
45
+ instant = Astronoby::Instant.from_time(time)
46
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
47
+
48
+ moon.current_phase_fraction.round(2)
49
+ # => 0.59
50
+ ```
51
+
52
+ ## Major Moon phases in the month
53
+
54
+ If you are interested to know when the major Moon phases will happen during a
55
+ civil month, you can use `Astronoby::Events::MoonPhases` and its class method
56
+ `::phases_for` with the key arguments `year` and `month`, both `Integer`.
57
+
58
+ It returns an array of `Astronoby::MoonPhase` objects, which each expose a
59
+ `phase` (`Symbol`) and a `time` (`Time`).
60
+
61
+ Please note that because a lunar month is around 29 days, some months will have
62
+ the same phase twice.
63
+
64
+ ```rb
65
+ phases = Astronoby::Events::MoonPhases.phases_for(year: 2024, month: 5)
66
+
67
+ phases.each { puts "#{_1.phase}: #{_1.time}" }
68
+ # last_quarter: 2024-05-01 11:27:15 UTC
69
+ # new_moon: 2024-05-08 03:21:56 UTC
70
+ # first_quarter: 2024-05-15 11:48:02 UTC
71
+ # full_moon: 2024-05-23 13:53:12 UTC
72
+ # last_quarter: 2024-05-30 17:12:43 UTC
73
+ ```
74
+
75
+ ## See also
76
+ - [Twilight Times](twilight_times.md) - for sun-related events
77
+ - [Rise, Transit and Set Times](rise_transit_set_times.md) - for moon events
78
+ - [Celestial Bodies](celestial_bodies.md) - for moon object details
79
+ - [Ephemerides](ephem.md) - for data sources
data/docs/observer.md ADDED
@@ -0,0 +1,65 @@
1
+ # Observer
2
+
3
+ `Astronoby::Observer` is the representation of an observer on Earth. Most of the
4
+ events computed by Astronoby are location and date based.
5
+
6
+ ## Initialization
7
+
8
+ The two required key arguments to instantiate an observer are:
9
+ * `latitude` (`Astronoby::Angle`): the angle from the equator to the observer,
10
+ from 90° to -90°, with positive angles for the Northern Hemisphere.
11
+ * `longitude` (`Astronoby::Angle`): the angle from the Greenwich meridian to the
12
+ observer, from 180° to -180°, with positive angles eastward of the Greenwich
13
+ meridian.
14
+
15
+ Latitude and longitude are defined according to the [World Geodetic System].
16
+ In other words, they are the same as those used for the [GPS].
17
+
18
+ It is also possible to give the following optional key arguments:
19
+ * `elevation` (`Astronoby::Distance`): the distance above or below the average
20
+ sea level
21
+ * `utc_offset`: local time difference with UTC. Check the [timezone specifiers]
22
+ for the format.
23
+
24
+ ```rb
25
+ # Location: Alhambra, Spain
26
+
27
+ observer = Astronoby::Observer.new(
28
+ latitude: Astronoby::Angle.from_degrees(37.176),
29
+ longitude: Astronoby::Angle.from_degrees(-3.588),
30
+ elevation: Astronoby::Distance.from_meters(792)
31
+ )
32
+ ```
33
+
34
+ You can learn more about angles on the [Angles page].
35
+
36
+ ## Value equality
37
+
38
+ `Astronoby::Observer` is a value object, which means it implements value
39
+ equality.
40
+
41
+ ```rb
42
+ observer1 = Astronoby::Observer.new(
43
+ latitude: Astronoby::Angle.from_degrees(90),
44
+ longitude: Astronoby::Angle.from_degrees(180)
45
+ )
46
+
47
+ observer2 = Astronoby::Observer.new(
48
+ latitude: Astronoby::Angle.from_hours(6),
49
+ longitude: Astronoby::Angle.from_hours(12)
50
+ )
51
+
52
+ observer1 == observer2
53
+ # => true
54
+ ```
55
+
56
+ [World Geodetic System]: https://en.wikipedia.org/wiki/World_Geodetic_System
57
+ [GPS]: https://en.wikipedia.org/wiki/GPS
58
+ [timezone specifiers]: https://ruby-doc.org/3.4.1/Time.html#class-Time-label-Timezone+Specifiers
59
+ [Angles page]: angles.md
60
+
61
+ ## See also
62
+ - [Angles](angles.md) - for working with latitude and longitude
63
+ - [Coordinates](coordinates.md) - for understanding position systems
64
+ - [Reference Frames](reference_frames.md) - for topocentric calculations
65
+ - [Celestial Bodies](celestial_bodies.md) - for observing objects
@@ -0,0 +1,138 @@
1
+ # Reference Frames
2
+
3
+ A given body at a given time can be perceived at different positions, depending
4
+ on the reference frame and the corrections applied.
5
+
6
+ Astronoby provides five reference frames for each celestial body:
7
+
8
+ * Geometric
9
+ * Astrometric
10
+ * Mean of date
11
+ * Apparent
12
+ * Topocentric
13
+
14
+ All reference frames provide this common interface:
15
+
16
+ * `#position`: Vector of position as x,y,z `Astronoby::Distance` objects
17
+ * `#velocity`: Vector of velocity as x,y,z `Astronoby::Velocity` objects
18
+ * `#distance`: Distance from the centre (`Astronoby::Distance`)
19
+ * `#equatorial`: Equatorial coordinates (`Astronoby::Coordinates::Equatorial`)
20
+ * `#ecliptic`: Ecliptic coordinates (`Astronoby::Coordinates::Ecliptic`)
21
+
22
+ ## Geometric
23
+
24
+ Also called "mean J2000", this reference frame is related to the mean ecliptic
25
+ or terrestrial equator and the mean equinox of the reference date (J2000). It is
26
+ the strict position computed from the ephemeris file in a reference frame
27
+ centered on the Solar System barycentre, with no corrections applied.
28
+
29
+ ```rb
30
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
31
+ time = Time.utc(1962, 7, 24)
32
+ instant = Astronoby::Instant.from_time(time)
33
+
34
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
35
+ geometric = moon.geometric
36
+ # => #<Astronoby::Geometric:0x000000011e7ffd40
37
+
38
+ geometric.distance.au
39
+ # => 1.0095091198501744
40
+
41
+ geometric.equatorial.right_ascension.str(:hms, precision: 0)
42
+ # => "20h 13m 52s"
43
+ ```
44
+
45
+ ## Astrometric
46
+
47
+ Also called "astrometric J2000", this reference frame is related to the ecliptic
48
+ or the mean terrestrial equator and the mean equinox of the reference date
49
+ (J2000). It applies light-time correction between the celestial body and the
50
+ observer. The frame is centred on the Earth's centre, as are all the following
51
+ reference frames.
52
+
53
+ ```rb
54
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
55
+ time = Time.utc(1962, 7, 24)
56
+ instant = Astronoby::Instant.from_time(time)
57
+
58
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
59
+ astrometric = moon.astrometric
60
+
61
+ astrometric.distance.km.round
62
+ # => 371187
63
+
64
+ astrometric.equatorial.right_ascension.str(:hms, precision: 0)
65
+ # => "1h 54m 27s"
66
+ ```
67
+
68
+ ## Mean of date
69
+
70
+ This reference frame is related to the ecliptic or the mean equator and the mean
71
+ equinox of the date. It provides the geometric position corrected for the
72
+ precessional motion of the Earth's rotation axis (precession and nutation).
73
+
74
+
75
+ ```rb
76
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
77
+ time = Time.utc(1962, 7, 24)
78
+ instant = Astronoby::Instant.from_time(time)
79
+
80
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
81
+ mean_of_date = moon.mean_of_date
82
+
83
+ mean_of_date.equatorial.right_ascension.str(:hms, precision: 0)
84
+ # => "1h 52m 29s"
85
+ ```
86
+
87
+ ## Apparent
88
+
89
+ This reference frame is related to the true ecliptic or equator and the true
90
+ equinox of the date. It is the actual position in the sky of a celestial object
91
+ as seen from the centre of the Earth. It applies several corrections to the
92
+ astrometric position:the deflection of light, the aberration, the precession and
93
+ the nutation.
94
+
95
+ ```rb
96
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
97
+ time = Time.utc(1962, 7, 24)
98
+ instant = Astronoby::Instant.from_time(time)
99
+
100
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
101
+ apparent = moon.apparent
102
+
103
+ apparent.equatorial.right_ascension.str(:hms, precision: 0)
104
+ # => "1h 52m 28s"
105
+ ```
106
+
107
+ ## Topocentric
108
+
109
+ This reference frame is the final transformation of a position. It provides the
110
+ apparent position of a celestial body as seen from a location on Earth. It can
111
+ only be produced given an observer (`Astronoby::Observer`). It provides another
112
+ set of coordinates: horizontal (`Astronoby::Coordinates::Horizontal`).
113
+
114
+ ```rb
115
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
116
+ time = Time.utc(1962, 7, 24)
117
+ instant = Astronoby::Instant.from_time(time)
118
+ observer = Astronoby::Observer.new(
119
+ latitude: Astronoby::Angle.from_degrees(48.838),
120
+ longitude: Astronoby::Angle.from_degrees(2.4843)
121
+ )
122
+
123
+ moon = Astronoby::Moon.new(ephem: ephem, instant: instant)
124
+ topocentric = moon.observed_by(observer)
125
+
126
+ topocentric.horizontal.azimuth.str(:dms, precision: 0)
127
+ # => "+90° 14′ 19″"
128
+ ```
129
+
130
+ You can learn more about observers on the [Observer page].
131
+
132
+ ## See also
133
+ - [Coordinates](coordinates.md) - for understanding coordinate systems
134
+ - [Observer](observer.md) - for location setup
135
+ - [Celestial Bodies](celestial_bodies.md) - for object positions
136
+ - [Ephemerides](ephem.md) - for data sources
137
+
138
+ [Observer page]: observer.md
@@ -0,0 +1,119 @@
1
+ # Rise, Transit and Set Times
2
+
3
+ Astronoby provides a calculator to compute all the rise, transit and set times
4
+ that will happen for a celestial body as observed from Earth during a period
5
+ of time: `Astronoby::RiseTransitSetCalculator`.
6
+
7
+ ## Initialization
8
+
9
+ Once instantiated, the calculator doesn't do anything yet, it waits for your
10
+ instruction.
11
+
12
+ It takes as key arguments:
13
+ * `body` (`Astronoby::SolarSystemBody`): any supported celestial body,
14
+ e.g. `Astronoby::Sun`
15
+ * `observer` (`Astronoby::Observer`): location on Earth of the observer
16
+ * `ephem`: ephemeris to provide the initial raw data
17
+
18
+ You can learn more about [celestial bodies] and [ephemerides].
19
+
20
+ ```rb
21
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
22
+
23
+ observer = Astronoby::Observer.new(
24
+ latitude: Astronoby::Angle.from_degrees(41.0082),
25
+ longitude: Astronoby::Angle.from_degrees(28.9784),
26
+ elevation: Astronoby::Distance.from_meters(40)
27
+ )
28
+
29
+ calculator = Astronoby::RiseTransitSetCalculator.new(
30
+ body: Astronoby::Saturn,
31
+ observer: observer,
32
+ ephem: ephem
33
+ )
34
+ ```
35
+
36
+ You can learn more about observers on the
37
+ [Observer page](https://github.com/rhannequin/astronoby/wiki/Observer).
38
+
39
+ ## `#events_between`
40
+
41
+ This is the main method of the calculator. It provides all the rising, transit
42
+ and setting times that will happen between two dates. It returns a
43
+ `Astronoby::RiseTransitSetEvents` object which exposes the methods
44
+ `#rising_times`, `#transit_times` and `#setting_times`.
45
+
46
+ ```rb
47
+ events = calculator.events_between(
48
+ Time.utc(2025, 5, 1),
49
+ Time.utc(2025, 5, 3)
50
+ )
51
+
52
+ events.rising_times
53
+ # => [2025-05-01 01:28:48 UTC, 2025-05-02 01:25:07 UTC]
54
+
55
+ events.transit_times
56
+ # => [2025-05-01 07:21:34 UTC, 2025-05-02 07:18:01 UTC]
57
+
58
+ events.setting_times
59
+ # => [2025-05-01 13:14:24 UTC, 2025-05-02 13:10:59 UTC]
60
+ ```
61
+
62
+ ## `#events_on`
63
+
64
+ You can call `#events_on` to compute the event times that will happen during a
65
+ civil day. You can provide a UTC offset to specify the boundaries of the civil
66
+ day for your location.
67
+
68
+ This method also returns a `Astronoby::RiseTransitSetEvents` object because some
69
+ celestial bodies could occasionally have the same event happen multiple times in
70
+ a single day. This is the case for the Moon, for example, which can seem to rise
71
+ twice in the same civil day because of its quick motion around the Earth.
72
+
73
+ ```rb
74
+ events = calculator.events_on(Date.new(2025, 5, 1))
75
+
76
+ events.rising_times
77
+ # => [2025-05-01 01:28:48 UTC]
78
+
79
+ events.transit_times
80
+ # => [2025-05-01 07:21:34 UTC]
81
+
82
+ events.setting_times
83
+ # => [2025-05-01 13:14:24 UTC]
84
+ ```
85
+
86
+ ## `#event_on`
87
+
88
+ For convenience, `Astronoby::RiseTransitSetCalculator` also exposes a
89
+ `#event_on` method that behaves the same way as `#events_on` but returns the
90
+ first time of rising, transit and setting for the civil date, as these events
91
+ only happen once in most cases. It returns a `Astronoby::RiseTransitSetEvent`
92
+ which exposes the instance methods `#rising_time`, `#transit_time` and
93
+ `#setting_time`.
94
+
95
+ ```rb
96
+ utc_offset = "+03:00"
97
+ event = calculator.event_on(
98
+ Date.new(2025, 5, 1),
99
+ utc_offset: utc_offset
100
+ )
101
+
102
+ event.rising_time.localtime(utc_offset)
103
+ # => 2025-05-01 04:28:48 +0300
104
+
105
+ event.transit_time.localtime(utc_offset)
106
+ # => 2025-05-01 10:21:34 +0300
107
+
108
+ event.setting_time.localtime(utc_offset)
109
+ # => 2025-05-01 16:14:24 +0300
110
+ ```
111
+
112
+ [celestial bodies]: celestial_bodies.md
113
+ [ephemerides]: ephem.md
114
+
115
+ ## See also
116
+ - [Twilight Times](twilight_times.md) - for sun-related events
117
+ - [Celestial Bodies](celestial_bodies.md) - for object information
118
+ - [Observer](observer.md) - for location setup
119
+ - [Ephemerides](ephem.md) - for data sources
@@ -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
@@ -1,47 +1,72 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Astronoby
4
+ # Applies relativistic aberration corrections to an astrometric position based
5
+ # on observer velocity.
4
6
  class Aberration
5
- MAXIMUM_SHIFT = Angle.from_degrees(20.5)
7
+ # Source:
8
+ # Title: Explanatory Supplement to the Astronomical Almanac
9
+ # Authors: Sean E. Urban and P. Kenneth Seidelmann
10
+ # Edition: University Science Books
11
+ # Chapter: 7.2.3 - Aberration
6
12
 
7
- def self.for_ecliptic_coordinates(coordinates:, epoch:)
8
- new(coordinates, epoch).apply
9
- end
13
+ LIGHT_SPEED = Astronoby::Velocity.light_speed.mps
10
14
 
11
- def initialize(coordinates, epoch)
12
- @coordinates = coordinates
13
- @epoch = epoch
15
+ # Initializes the aberration correction with position and observer velocity.
16
+ #
17
+ # @param astrometric_position [Astronoby::Vector<Astronoby::Distance>] The
18
+ # astrometric position vector.
19
+ # @param observer_velocity [Astronoby::Vector<Astronoby::Velocity>] The
20
+ # velocity vector of the observer.
21
+ def initialize(astrometric_position:, observer_velocity:)
22
+ @position = astrometric_position
23
+ @velocity = observer_velocity
24
+ @distance_meters = @position.norm.m
25
+ @observer_speed = @velocity.norm.mps
14
26
  end
15
27
 
16
- # Source:
17
- # Title: Practical Astronomy with your Calculator or Spreadsheet
18
- # Authors: Peter Duffett-Smith and Jonathan Zwart
19
- # Edition: Cambridge University Press
20
- # Chapter: 36 - Aberration
21
- def apply
22
- delta_longitude = Angle.from_degrees(
23
- -MAXIMUM_SHIFT.degrees * (
24
- sun_longitude - @coordinates.longitude
25
- ).cos / @coordinates.latitude.cos / Constants::SECONDS_PER_DEGREE
26
- )
28
+ # Computes the aberration-corrected position.
29
+ #
30
+ # @return [Astronoby::Vector<Astronoby::Distance>] The corrected position
31
+ # vector.
32
+ def corrected_position
33
+ beta = @observer_speed / LIGHT_SPEED
34
+ projected_velocity = beta * aberration_angle_cos
35
+ lorentz_factor_inv = lorentz_factor_inverse(beta)
27
36
 
28
- delta_latitude = Angle.from_degrees(
29
- -MAXIMUM_SHIFT.degrees *
30
- (sun_longitude - @coordinates.longitude).sin *
31
- @coordinates.latitude.sin / Constants::SECONDS_PER_DEGREE
32
- )
37
+ velocity_correction =
38
+ velocity_correction_factor(projected_velocity) * velocity_mps
39
+ normalization_factor = 1.0 + projected_velocity
40
+ position_scaled = position_meters * lorentz_factor_inv
33
41
 
34
- Coordinates::Ecliptic.new(
35
- latitude: @coordinates.latitude + delta_latitude,
36
- longitude: @coordinates.longitude + delta_longitude
42
+ Distance.vector_from_meters(
43
+ (position_scaled + velocity_correction) / normalization_factor
37
44
  )
38
45
  end
39
46
 
40
- def sun_longitude
41
- @_sun_longitude ||= Sun
42
- .new(time: Epoch.to_utc(@epoch))
43
- .true_ecliptic_coordinates
44
- .longitude
47
+ private
48
+
49
+ def aberration_angle_cos
50
+ denominator = [@distance_meters * @observer_speed, 1e-20].max
51
+ Util::Maths.dot_product(position_meters, velocity_mps) / denominator
52
+ end
53
+
54
+ def position_meters
55
+ @position.map(&:meters)
56
+ end
57
+
58
+ def velocity_mps
59
+ @velocity.map(&:mps)
60
+ end
61
+
62
+ def lorentz_factor_inverse(beta)
63
+ Math.sqrt(1.0 - beta**2)
64
+ end
65
+
66
+ def velocity_correction_factor(projected_velocity)
67
+ lorentz_inv = lorentz_factor_inverse(projected_velocity)
68
+ (1.0 + projected_velocity / (1.0 + lorentz_inv)) *
69
+ (@distance_meters / LIGHT_SPEED)
45
70
  end
46
71
  end
47
72
  end