astronoby 0.3.0 → 0.5.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/Gemfile.lock +19 -17
  4. data/README.md +195 -25
  5. data/UPGRADING.md +72 -0
  6. data/lib/astronoby/aberration.rb +7 -5
  7. data/lib/astronoby/angle.rb +26 -34
  8. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +143 -0
  9. data/lib/astronoby/astronomical_models/moon_phases_periodic_terms.rb +249 -0
  10. data/lib/astronoby/bodies/moon.rb +335 -0
  11. data/lib/astronoby/bodies/sun.rb +129 -132
  12. data/lib/astronoby/constants.rb +31 -0
  13. data/lib/astronoby/coordinates/ecliptic.rb +4 -4
  14. data/lib/astronoby/coordinates/equatorial.rb +7 -5
  15. data/lib/astronoby/coordinates/horizontal.rb +24 -12
  16. data/lib/astronoby/distance.rb +83 -0
  17. data/lib/astronoby/epoch.rb +0 -2
  18. data/lib/astronoby/equinox_solstice.rb +3 -2
  19. data/lib/astronoby/events/moon_phases.rb +143 -0
  20. data/lib/astronoby/events/observation_events.rb +259 -0
  21. data/lib/astronoby/events/rise_transit_set_iteration.rb +215 -0
  22. data/lib/astronoby/events/twilight_events.rb +121 -0
  23. data/lib/astronoby/geocentric_parallax.rb +36 -56
  24. data/lib/astronoby/mean_obliquity.rb +2 -2
  25. data/lib/astronoby/moon_phase.rb +43 -0
  26. data/lib/astronoby/nutation.rb +5 -3
  27. data/lib/astronoby/observer.rb +39 -18
  28. data/lib/astronoby/precession.rb +1 -1
  29. data/lib/astronoby/refraction.rb +8 -10
  30. data/lib/astronoby/time/greenwich_sidereal_time.rb +18 -29
  31. data/lib/astronoby/time/local_sidereal_time.rb +4 -4
  32. data/lib/astronoby/util/maths.rb +72 -0
  33. data/lib/astronoby/util/time.rb +88 -0
  34. data/lib/astronoby/util/trigonometry.rb +4 -4
  35. data/lib/astronoby/version.rb +1 -1
  36. data/lib/astronoby.rb +12 -1
  37. metadata +15 -4
  38. data/lib/astronoby/body.rb +0 -155
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class RiseTransitSetIteration
5
+ EARTH_SIDEREAL_ROTATION_RATE = 360.98564736629
6
+
7
+ # Source:
8
+ # Title: Astronomical Algorithms
9
+ # Author: Jean Meeus
10
+ # Edition: 2nd edition
11
+ # Chapter: 15 - Rising, Transit, and Setting
12
+
13
+ # @param observer [Astronoby::Observer] Observer
14
+ # @param date [Date] Date of the event
15
+ # @param coordinates_of_the_previous_day [Astronoby::Coordinates::Equatorial]
16
+ # Coordinates of the body of the previous day
17
+ # @param coordinates_of_the_day [Astronoby::Coordinates::Equatorial]
18
+ # Coordinates of the body of the day
19
+ # @param coordinates_of_the_next_day [Astronoby::Coordinates::Equatorial]
20
+ # Coordinates of the body of the next day
21
+ # @param shift [Astronoby::Angle] Altitude shift
22
+ # @param initial_rising [Float] Initial rising
23
+ # @param initial_transit [Float] Initial transit
24
+ # @param initial_setting [Float] Initial setting
25
+ def initialize(
26
+ observer:,
27
+ date:,
28
+ coordinates_of_the_previous_day:,
29
+ coordinates_of_the_day:,
30
+ coordinates_of_the_next_day:,
31
+ shift:,
32
+ initial_rising:,
33
+ initial_transit:,
34
+ initial_setting:
35
+ )
36
+ @observer = observer
37
+ @date = date
38
+ @coordinates_of_the_previous_day = coordinates_of_the_previous_day
39
+ @coordinates_of_the_day = coordinates_of_the_day
40
+ @coordinates_of_the_next_day = coordinates_of_the_next_day
41
+ @shift = shift
42
+ @initial_rising = initial_rising
43
+ @initial_transit = initial_transit
44
+ @initial_setting = initial_setting
45
+ end
46
+
47
+ # @return [Array<Float>] Iteration results
48
+ def iterate
49
+ [
50
+ delta_m_rising,
51
+ delta_m_transit,
52
+ delta_m_setting
53
+ ]
54
+ end
55
+
56
+ private
57
+
58
+ def delta_m_rising
59
+ (local_horizontal_altitude_rising - @shift).degrees./(
60
+ Constants::DEGREES_PER_CIRCLE *
61
+ declination_rising.cos *
62
+ @observer.latitude.cos *
63
+ local_hour_angle_rising.sin
64
+ )
65
+ end
66
+
67
+ def delta_m_transit
68
+ -local_hour_angle_transit.degrees / Constants::DEGREES_PER_CIRCLE
69
+ end
70
+
71
+ def delta_m_setting
72
+ (local_horizontal_altitude_setting - @shift).degrees./(
73
+ Constants::DEGREES_PER_CIRCLE *
74
+ declination_setting.cos *
75
+ @observer.latitude.cos *
76
+ local_hour_angle_setting.sin
77
+ )
78
+ end
79
+
80
+ def observer_longitude
81
+ # Longitude must be treated positively westwards from the meridian of
82
+ # Greenwich, and negatively to the east
83
+ @observer_longitude ||= -@observer.longitude
84
+ end
85
+
86
+ def apparent_gst_at_midnight
87
+ @apparent_gst_at_midnight ||= Angle.from_hours(
88
+ GreenwichSiderealTime.from_utc(
89
+ Time.utc(@date.year, @date.month, @date.day)
90
+ ).time
91
+ )
92
+ end
93
+
94
+ def gst_rising
95
+ @gst_rising ||= Angle.from_degrees(
96
+ apparent_gst_at_midnight.degrees +
97
+ EARTH_SIDEREAL_ROTATION_RATE * @initial_rising
98
+ )
99
+ end
100
+
101
+ def gst_transit
102
+ @gst_transit ||= Angle.from_degrees(
103
+ apparent_gst_at_midnight.degrees +
104
+ EARTH_SIDEREAL_ROTATION_RATE * @initial_transit
105
+ )
106
+ end
107
+
108
+ def gst_setting
109
+ @gst_setting ||= Angle.from_degrees(
110
+ apparent_gst_at_midnight.degrees +
111
+ EARTH_SIDEREAL_ROTATION_RATE * @initial_setting
112
+ )
113
+ end
114
+
115
+ def leap_day_portion
116
+ @leap_day_portion ||= begin
117
+ leap_seconds = Util::Time.terrestrial_universal_time_delta(@date)
118
+ leap_seconds / Constants::SECONDS_PER_DAY
119
+ end
120
+ end
121
+
122
+ def local_hour_angle_rising
123
+ @local_hour_angle_rising ||=
124
+ gst_rising - observer_longitude - right_ascension_rising
125
+ end
126
+
127
+ def local_hour_angle_transit
128
+ gst_transit - observer_longitude - right_ascension_transit
129
+ end
130
+
131
+ def local_hour_angle_setting
132
+ @local_hour_angle_setting ||=
133
+ gst_setting - observer_longitude - right_ascension_setting
134
+ end
135
+
136
+ def local_horizontal_altitude_rising
137
+ Angle.asin(
138
+ @observer.latitude.sin * declination_rising.sin +
139
+ @observer.latitude.cos * declination_rising.cos * local_hour_angle_rising.cos
140
+ )
141
+ end
142
+
143
+ def local_horizontal_altitude_setting
144
+ Angle.asin(
145
+ @observer.latitude.sin * declination_setting.sin +
146
+ @observer.latitude.cos * declination_setting.cos * local_hour_angle_setting.cos
147
+ )
148
+ end
149
+
150
+ def right_ascension_rising
151
+ Angle.from_degrees(
152
+ Util::Maths.interpolate(
153
+ [
154
+ @coordinates_of_the_previous_day.right_ascension.degrees,
155
+ @coordinates_of_the_day.right_ascension.degrees,
156
+ @coordinates_of_the_next_day.right_ascension.degrees
157
+ ],
158
+ @initial_rising + leap_day_portion
159
+ )
160
+ )
161
+ end
162
+
163
+ def right_ascension_transit
164
+ Angle.from_degrees(
165
+ Util::Maths.interpolate(
166
+ [
167
+ @coordinates_of_the_previous_day.right_ascension.degrees,
168
+ @coordinates_of_the_day.right_ascension.degrees,
169
+ @coordinates_of_the_next_day.right_ascension.degrees
170
+ ],
171
+ @initial_transit + leap_day_portion
172
+ )
173
+ )
174
+ end
175
+
176
+ def right_ascension_setting
177
+ Angle.from_degrees(
178
+ Util::Maths.interpolate(
179
+ [
180
+ @coordinates_of_the_previous_day.right_ascension.degrees,
181
+ @coordinates_of_the_day.right_ascension.degrees,
182
+ @coordinates_of_the_next_day.right_ascension.degrees
183
+ ],
184
+ @initial_setting + leap_day_portion
185
+ )
186
+ )
187
+ end
188
+
189
+ def declination_rising
190
+ Angle.from_degrees(
191
+ Util::Maths.interpolate(
192
+ [
193
+ @coordinates_of_the_previous_day.declination.degrees,
194
+ @coordinates_of_the_day.declination.degrees,
195
+ @coordinates_of_the_next_day.declination.degrees
196
+ ],
197
+ @initial_rising + leap_day_portion
198
+ )
199
+ )
200
+ end
201
+
202
+ def declination_setting
203
+ Angle.from_degrees(
204
+ Util::Maths.interpolate(
205
+ [
206
+ @coordinates_of_the_previous_day.declination.degrees,
207
+ @coordinates_of_the_day.declination.degrees,
208
+ @coordinates_of_the_next_day.declination.degrees
209
+ ],
210
+ @initial_setting + leap_day_portion
211
+ )
212
+ )
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ module Events
5
+ class TwilightEvents
6
+ TWILIGHTS = [
7
+ CIVIL = :civil,
8
+ NAUTICAL = :nautical,
9
+ ASTRONOMICAL = :astronomical
10
+ ].freeze
11
+
12
+ TWILIGHT_ANGLES = {
13
+ CIVIL => Angle.from_degrees(96),
14
+ NAUTICAL => Angle.from_degrees(102),
15
+ ASTRONOMICAL => Angle.from_degrees(108)
16
+ }.freeze
17
+
18
+ PERIODS_OF_THE_DAY = [
19
+ MORNING = :morning,
20
+ EVENING = :evening
21
+ ].freeze
22
+
23
+ attr_reader :morning_civil_twilight_time,
24
+ :evening_civil_twilight_time,
25
+ :morning_nautical_twilight_time,
26
+ :evening_nautical_twilight_time,
27
+ :morning_astronomical_twilight_time,
28
+ :evening_astronomical_twilight_time
29
+
30
+ def initialize(observer:, sun:)
31
+ @observer = observer
32
+ @sun = sun
33
+ PERIODS_OF_THE_DAY.each do |period_of_the_day|
34
+ TWILIGHT_ANGLES.each do |twilight, _|
35
+ zenith_angle = TWILIGHT_ANGLES[twilight]
36
+ instance_variable_set(
37
+ :"@#{period_of_the_day}_#{twilight}_twilight_time",
38
+ compute(period_of_the_day, zenith_angle)
39
+ )
40
+ end
41
+ end
42
+ end
43
+
44
+ # @param period_of_the_day [Symbol] :morning or :evening
45
+ # @param zenith_angle [Angle] The zenith angle of the twilight
46
+ def time_for_zenith_angle(period_of_the_day:, zenith_angle:)
47
+ unless PERIODS_OF_THE_DAY.include?(period_of_the_day)
48
+ raise IncompatibleArgumentsError,
49
+ "Only #{PERIODS_OF_THE_DAY.join(" or ")} are allowed as period_of_the_day, got #{period_of_the_day}"
50
+ end
51
+
52
+ compute(period_of_the_day, zenith_angle)
53
+ end
54
+
55
+ private
56
+
57
+ # Source:
58
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
59
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
60
+ # Edition: Cambridge University Press
61
+ # Chapter: 50 - Twilight
62
+ def compute(period_of_the_day, zenith_angle)
63
+ period_time = if period_of_the_day == MORNING
64
+ observation_events.rising_time
65
+ else
66
+ observation_events.setting_time
67
+ end
68
+
69
+ hour_angle_at_period = equatorial_coordinates_at_midday
70
+ .compute_hour_angle(time: period_time, longitude: @observer.longitude)
71
+
72
+ term1 = zenith_angle.cos -
73
+ @observer.latitude.sin *
74
+ @equatorial_coordinates_at_midday.declination.sin
75
+ term2 = @observer.latitude.cos *
76
+ equatorial_coordinates_at_midday.declination.cos
77
+ hour_angle_ratio_at_twilight = term1 / term2
78
+
79
+ return unless hour_angle_ratio_at_twilight.between?(-1, 1)
80
+
81
+ hour_angle_at_twilight = Angle.acos(hour_angle_ratio_at_twilight)
82
+ time_sign = -1
83
+
84
+ if period_of_the_day == MORNING
85
+ hour_angle_at_twilight = Angle.from_degrees(
86
+ Constants::DEGREES_PER_CIRCLE - hour_angle_at_twilight.degrees
87
+ )
88
+ time_sign = 1
89
+ end
90
+
91
+ twilight_in_hours =
92
+ time_sign * (hour_angle_at_twilight - hour_angle_at_period).hours *
93
+ GreenwichSiderealTime::SIDEREAL_MINUTE_IN_UT_MINUTE
94
+ twilight_in_seconds = time_sign *
95
+ twilight_in_hours *
96
+ Constants::SECONDS_PER_HOUR
97
+
98
+ (period_time + twilight_in_seconds).round
99
+ end
100
+
101
+ def observation_events
102
+ @observation_events ||= @sun.observation_events(observer: @observer)
103
+ end
104
+
105
+ def midday
106
+ date = @sun.time.to_date
107
+ Time.utc(date.year, date.month, date.day, 12)
108
+ end
109
+
110
+ def sun_at_midday
111
+ Sun.new(time: midday)
112
+ end
113
+
114
+ def equatorial_coordinates_at_midday
115
+ @equatorial_coordinates_at_midday ||= sun_at_midday
116
+ .apparent_ecliptic_coordinates
117
+ .to_apparent_equatorial(epoch: Epoch.from_time(midday))
118
+ end
119
+ end
120
+ end
121
+ end
@@ -8,104 +8,82 @@ module Astronoby
8
8
  # Edition: Cambridge University Press
9
9
  # Chapter: 39 - Calculating correction for parallax
10
10
 
11
- ASTRONOMICAL_UNIT_IN_METERS = 149_597_870_700
12
- EARTH_FLATTENING_CORRECTION = BigDecimal("0.996647")
13
- EARTH_EQUATORIAL_RADIUS = BigDecimal("6378140")
14
-
15
11
  # Equatorial horizontal parallax
16
- # @param distance [Numeric] Distance of the body from the center of the
17
- # Earth, in meters
12
+ # @param distance [Astronoby::Distance] Distance of the body from the center
13
+ # of the Earth
18
14
  # @return [Astronoby::Angle] Equatorial horizontal parallax angle
19
15
  def self.angle(distance:)
20
- distance_in_earth_radius = distance / EARTH_EQUATORIAL_RADIUS
21
- Angle.asin(1 / distance_in_earth_radius)
16
+ Angle.from_radians(Angle.from_dms(0, 0, 8.794).sin / distance.au)
22
17
  end
23
18
 
24
19
  # Correct equatorial coordinates with the equatorial horizontal parallax
25
- # @param latitude [Astronoby::Angle] Observer's latitude
26
- # @param longitude [Astronoby::Angle] Observer's longitude
27
- # @param elevation [Numeric] Observer's elevation above sea level in meters
20
+ # @param observer [Astronoby::Observer] Observer
28
21
  # @param time [Time] Date-time of the observation
29
22
  # @param coordinates [Astronoby::Coordinates::Equatorial]
30
23
  # Equatorial coordinates of the observed body
31
- # @param distance [Numeric] Distance of the observed body from the center of
32
- # the Earth, in meters
24
+ # @param distance [Astronoby::Distance] Distance of the observed body from
25
+ # the center of the Earth
33
26
  # @return [Astronoby::Coordinates::Equatorial] Apparent equatorial
34
27
  # coordinates with equatorial horizontal parallax
35
28
  def self.for_equatorial_coordinates(
36
- latitude:,
37
- longitude:,
38
- elevation:,
29
+ observer:,
39
30
  time:,
40
31
  coordinates:,
41
32
  distance:
42
33
  )
43
34
  new(
44
- latitude,
45
- longitude,
46
- elevation,
35
+ observer,
47
36
  time,
48
37
  coordinates,
49
38
  distance
50
39
  ).apply
51
40
  end
52
41
 
53
- # @param latitude [Astronoby::Angle] Observer's latitude
54
- # @param longitude [Astronoby::Angle] Observer's longitude
55
- # @param elevation [Numeric] Observer's elevation above sea level in meters
42
+ # @param observer [Astronoby::Observer] Observer
56
43
  # @param time [Time] Date-time of the observation
57
44
  # @param coordinates [Astronoby::Coordinates::Equatorial] Equatorial
58
45
  # coordinates of the observed body
59
- # @param distance [Numeric] Distance of the observed body from the center of
60
- # the Earth, in meters
46
+ # @param distance [Astronoby::Distance] Distance of the observed body from
47
+ # the center of the Earth
61
48
  def initialize(
62
- latitude,
63
- longitude,
64
- elevation,
49
+ observer,
65
50
  time,
66
51
  coordinates,
67
52
  distance
68
53
  )
69
- @latitude = latitude
70
- @longitude = longitude
71
- @elevation = elevation
54
+ @observer = observer
72
55
  @time = time
73
56
  @coordinates = coordinates
74
57
  @distance = distance
75
58
  end
76
59
 
77
60
  def apply
78
- term1 = Angle.atan(EARTH_FLATTENING_CORRECTION * @latitude.tan)
79
- quantity1 = term1.cos + elevation_ratio * @latitude.cos
80
- quantity2 = EARTH_FLATTENING_CORRECTION * term1.sin +
81
- elevation_ratio * @latitude.sin
82
-
83
- term2 = quantity1 * hour_angle.sin
84
- term3 = distance_in_earth_radius * declination.cos -
85
- quantity1 * hour_angle.cos
61
+ term1 = Angle.atan(Constants::EARTH_FLATTENING_CORRECTION * latitude.tan)
62
+ quantity1 = term1.cos + elevation_ratio * latitude.cos
63
+ quantity2 = Constants::EARTH_FLATTENING_CORRECTION * term1.sin +
64
+ elevation_ratio * latitude.sin
86
65
 
87
- delta = Angle.atan(term2 / term3)
66
+ term1 = -quantity1 * equatorial_horizontal_parallax.sin * hour_angle.sin
67
+ term2 = declination.cos - quantity1 * equatorial_horizontal_parallax.sin * hour_angle.cos
68
+ delta_right_ascension = Angle.atan(term1 / term2)
88
69
 
89
- apparent_hour_angle = hour_angle + delta
90
- apparent_right_ascension = right_ascension - delta
91
- apparent_declination = Angle.atan(
92
- (
93
- apparent_hour_angle.cos *
94
- (distance_in_earth_radius * declination.sin - quantity2)
95
- ) / (
96
- distance_in_earth_radius * declination.cos * hour_angle.cos - quantity1
97
- )
98
- )
70
+ term1 = (declination.sin - quantity2 * equatorial_horizontal_parallax.sin) * delta_right_ascension.cos
71
+ term2 = declination.cos - quantity1 * equatorial_horizontal_parallax.sin * hour_angle.cos
72
+ new_declination = Angle.atan(term1 / term2)
99
73
 
100
74
  Coordinates::Equatorial.new(
101
- right_ascension: apparent_right_ascension,
102
- declination: apparent_declination,
75
+ right_ascension: delta_right_ascension + right_ascension,
76
+ declination: new_declination,
103
77
  epoch: @coordinates.epoch
104
78
  )
105
79
  end
106
80
 
107
81
  private
108
82
 
83
+ def latitude
84
+ @observer.latitude
85
+ end
86
+
109
87
  def right_ascension
110
88
  @coordinates.right_ascension
111
89
  end
@@ -115,16 +93,18 @@ module Astronoby
115
93
  end
116
94
 
117
95
  def hour_angle
118
- @_hour_angle ||=
119
- @coordinates.compute_hour_angle(time: @time, longitude: @longitude)
96
+ @_hour_angle ||= @coordinates.compute_hour_angle(
97
+ time: @time,
98
+ longitude: @observer.longitude
99
+ )
120
100
  end
121
101
 
122
102
  def elevation_ratio
123
- @elevation / EARTH_EQUATORIAL_RADIUS
103
+ @observer.elevation.meters / Constants::EARTH_EQUATORIAL_RADIUS_IN_METERS.to_f
124
104
  end
125
105
 
126
- def distance_in_earth_radius
127
- @distance / EARTH_EQUATORIAL_RADIUS
106
+ def equatorial_horizontal_parallax
107
+ self.class.angle(distance: @distance)
128
108
  end
129
109
  end
130
110
  end
@@ -14,14 +14,14 @@ module Astronoby
14
14
  def self.for_epoch(epoch)
15
15
  return obliquity_of_reference if epoch == EPOCH_OF_REFERENCE
16
16
 
17
- t = (epoch - EPOCH_OF_REFERENCE) / Epoch::DAYS_PER_JULIAN_CENTURY
17
+ t = (epoch - EPOCH_OF_REFERENCE) / Constants::DAYS_PER_JULIAN_CENTURY
18
18
 
19
19
  Angle.from_degrees(
20
20
  obliquity_of_reference.degrees - (
21
21
  46.815 * t -
22
22
  0.0006 * t * t +
23
23
  0.00181 * t * t * t
24
- ) / 3600
24
+ ) / Constants::SECONDS_PER_DEGREE
25
25
  )
26
26
  end
27
27
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class MoonPhase
5
+ NEW_MOON = :new_moon
6
+ FIRST_QUARTER = :first_quarter
7
+ FULL_MOON = :full_moon
8
+ LAST_QUARTER = :last_quarter
9
+
10
+ attr_reader :time, :phase
11
+
12
+ # @param time [Time] Time of the Moon phase
13
+ # @return [Astronoby::MoonPhase] New Moon phase
14
+ def self.new_moon(time)
15
+ new(time: time, phase: NEW_MOON)
16
+ end
17
+
18
+ # @param time [Time] Time of the Moon phase
19
+ # @return [Astronoby::MoonPhase] First quarter Moon phase
20
+ def self.first_quarter(time)
21
+ new(time: time, phase: FIRST_QUARTER)
22
+ end
23
+
24
+ # @param time [Time] Time of the Moon phase
25
+ # @return [Astronoby::MoonPhase] Full Moon phase
26
+ def self.full_moon(time)
27
+ new(time: time, phase: FULL_MOON)
28
+ end
29
+
30
+ # @param time [Time] Time of the Moon phase
31
+ # @return [Astronoby::MoonPhase] Last quarter Moon phase
32
+ def self.last_quarter(time)
33
+ new(time: time, phase: LAST_QUARTER)
34
+ end
35
+
36
+ # @param time [Time] Time of the Moon phase
37
+ # @param phase [Symbol] Moon phase
38
+ def initialize(time:, phase:)
39
+ @time = time
40
+ @phase = phase
41
+ end
42
+ end
43
+ end
@@ -45,18 +45,20 @@ module Astronoby
45
45
  private
46
46
 
47
47
  def julian_centuries
48
- (@epoch - Epoch::J1900) / Epoch::DAYS_PER_JULIAN_CENTURY
48
+ (@epoch - Epoch::J1900) / Constants::DAYS_PER_JULIAN_CENTURY
49
49
  end
50
50
 
51
51
  def sun_mean_longitude
52
52
  Angle.from_degrees(
53
- (279.6967 + 360.0 * (centuries_a - centuries_a.to_i)) % 360
53
+ (279.6967 + Constants::DEGREES_PER_CIRCLE * (centuries_a - centuries_a.to_i)) %
54
+ Constants::DEGREES_PER_CIRCLE
54
55
  )
55
56
  end
56
57
 
57
58
  def moon_ascending_node_longitude
58
59
  Angle.from_degrees(
59
- (259.1833 - 360.0 * (centuries_b - centuries_b.to_i)) % 360
60
+ (259.1833 - Constants::DEGREES_PER_CIRCLE * (centuries_b - centuries_b.to_i)) %
61
+ Constants::DEGREES_PER_CIRCLE
60
62
  )
61
63
  end
62
64
 
@@ -2,20 +2,20 @@
2
2
 
3
3
  module Astronoby
4
4
  class Observer
5
- DEFAULT_ELEVATION = 0
6
- DEFAULT_TEMPERATURE = BigDecimal("283.15")
7
- PRESSURE_AT_SEA_LEVEL = BigDecimal("1013.25")
8
- PASCAL_PER_MILLIBAR = BigDecimal("0.01")
9
- EARTH_GRAVITATIONAL_ACCELERATION = BigDecimal("9.80665")
10
- MOLAR_MASS_OF_AIR = BigDecimal("0.0289644")
11
- UNIVERSAL_GAS_CONSTANT = BigDecimal("8.31432")
5
+ DEFAULT_ELEVATION = Distance.zero
6
+ DEFAULT_TEMPERATURE = 283.15
7
+ PRESSURE_AT_SEA_LEVEL = 1013.25
8
+ PASCAL_PER_MILLIBAR = 0.01
9
+ EARTH_GRAVITATIONAL_ACCELERATION = 9.80665
10
+ MOLAR_MASS_OF_AIR = 0.0289644
11
+ UNIVERSAL_GAS_CONSTANT = 8.31432
12
12
 
13
- attr_reader :latitude, :longitude, :elevation, :temperature
13
+ attr_reader :latitude, :longitude, :elevation, :temperature, :pressure
14
14
 
15
15
  # @param latitude [Angle] geographic latitude of the observer
16
16
  # @param longitude [Angle] geographic longitude of the observer
17
- # @param elevation [Numeric] geographic elevation (or altitude) of the
18
- # observer above sea level in meters
17
+ # @param elevation [Astronoby::Distance] geographic elevation (or altitude)
18
+ # of the observer above sea level
19
19
  # @param temperature [Numeric] temperature at the observer's location in
20
20
  # kelvins
21
21
  # @param pressure [Numeric] atmospheric pressure at the observer's
@@ -31,24 +31,45 @@ module Astronoby
31
31
  @longitude = longitude
32
32
  @elevation = elevation
33
33
  @temperature = temperature
34
- @pressure = pressure
34
+ @pressure = pressure || compute_pressure
35
35
  end
36
36
 
37
- # Compute an estimation of the atmospheric pressure based on the elevation
38
- # and temperature
39
- #
40
- # @return [BigDecimal] the atmospheric pressure in millibars.
41
- def pressure
42
- @pressure ||= PRESSURE_AT_SEA_LEVEL * pressure_ratio
37
+ def ==(other)
38
+ return false unless other.is_a?(self.class)
39
+
40
+ @latitude == other.latitude &&
41
+ @longitude == other.longitude &&
42
+ @elevation == other.elevation &&
43
+ @temperature == other.temperature &&
44
+ @pressure == other.pressure
45
+ end
46
+ alias_method :eql?, :==
47
+
48
+ def hash
49
+ [
50
+ self.class,
51
+ @latitude,
52
+ @longitude,
53
+ @elevation,
54
+ @temperature,
55
+ @pressure
56
+ ].hash
43
57
  end
44
58
 
45
59
  private
46
60
 
61
+ # @return [Float] the atmospheric pressure in millibars.
62
+ def compute_pressure
63
+ @pressure ||= PRESSURE_AT_SEA_LEVEL * pressure_ratio
64
+ end
65
+
47
66
  # Source:
48
67
  # Barometric formula
49
68
  # https://en.wikipedia.org/wiki/Barometric_formula
50
69
  def pressure_ratio
51
- term1 = EARTH_GRAVITATIONAL_ACCELERATION * MOLAR_MASS_OF_AIR * @elevation
70
+ term1 = EARTH_GRAVITATIONAL_ACCELERATION *
71
+ MOLAR_MASS_OF_AIR *
72
+ @elevation.meters
52
73
  term2 = UNIVERSAL_GAS_CONSTANT * @temperature
53
74
 
54
75
  Math.exp(-term1 / term2)
@@ -45,7 +45,7 @@ module Astronoby
45
45
  private
46
46
 
47
47
  def matrix_for_epoch(epoch)
48
- t = (epoch - Epoch::DEFAULT_EPOCH) / Epoch::DAYS_PER_JULIAN_CENTURY
48
+ t = (epoch - Epoch::DEFAULT_EPOCH) / Constants::DAYS_PER_JULIAN_CENTURY
49
49
 
50
50
  zeta = Angle.from_degrees(
51
51
  0.6406161 * t + 0.0000839 * t * t + 0.000005 * t * t * t