astronoby 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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