astronoby 0.1.0 → 0.2.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.
@@ -2,79 +2,154 @@
2
2
 
3
3
  module Astronoby
4
4
  class Body
5
+ DEFAULT_REFRACTION_VERTICAL_SHIFT = Angle.as_dms(0, 34, 0)
6
+ RISING_SETTING_HOUR_ANGLE_RATIO_RANGE = (-1..1)
7
+
5
8
  def initialize(equatorial_coordinates)
6
9
  @equatorial_coordinates = equatorial_coordinates
7
10
  end
8
11
 
9
12
  # Source:
10
- # Title: Celestial Calculations
11
- # Author: J. L. Lawrence
12
- # Edition: MIT Press
13
- # Chapter: 5 - Stars in the Nighttime Sky
14
- def rising_time(latitude:, longitude:, date:)
15
- h2_component = h2(latitude: latitude)
16
- return nil if h2_component.nil?
17
-
18
- rising_lst = 24 +
19
- @equatorial_coordinates.right_ascension.hours - h2_component.degrees
20
- rising_lst -= 24 if rising_lst > 24
21
-
22
- Util::Time.lst_to_ut(date: date, longitude: longitude, lst: rising_lst)
13
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
14
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
15
+ # Edition: Cambridge University Press
16
+ # Chapter: 33 - Rising and setting
17
+
18
+ # @param latitude [Astronoby::Angle] Latitude of the observer
19
+ # @param longitude [Astronoby::Angle] Longitude of the observer
20
+ # @param date [Date] Date of the event
21
+ # @param apparent [Boolean] Compute apparent or true data
22
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
23
+ # @return [Time, nil] Sunrise time
24
+ def rising_time(
25
+ latitude:,
26
+ longitude:,
27
+ date:,
28
+ apparent: true,
29
+ vertical_shift: nil
30
+ )
31
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
32
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
33
+
34
+ hour_angle = Angle.acos(time_ratio)
35
+ local_sidereal_time = LocalSiderealTime.new(
36
+ date: date,
37
+ time: right_ascension.hours - hour_angle.hours,
38
+ longitude: longitude
39
+ )
40
+
41
+ local_sidereal_time.to_gst.to_utc
23
42
  end
24
43
 
25
44
  # Source:
26
- # Title: Celestial Calculations
27
- # Author: J. L. Lawrence
28
- # Edition: MIT Press
29
- # Chapter: 5 - Stars in the Nighttime Sky
30
- def rising_azimuth(latitude:)
31
- ar = azimuth_component(latitude: latitude)
32
- return nil if ar >= 1
33
-
34
- Angle.acos(ar)
45
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
46
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
47
+ # Edition: Cambridge University Press
48
+ # Chapter: 33 - Rising and setting
49
+
50
+ # @param latitude [Astronoby::Angle] Latitude of the observer
51
+ # @param apparent [Boolean] Compute apparent or true data
52
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
53
+ # @return [Astronoby::Angle, nil] Sunrise azimuth
54
+ def rising_azimuth(latitude:, apparent: true, vertical_shift: nil)
55
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
56
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
57
+
58
+ azimuth_ratio = azimuth_ratio(latitude, apparent, vertical_shift)
59
+
60
+ Angle.acos(azimuth_ratio)
35
61
  end
36
62
 
37
63
  # Source:
38
- # Title: Celestial Calculations
39
- # Author: J. L. Lawrence
40
- # Edition: MIT Press
41
- # Chapter: 5 - Stars in the Nighttime Sky
42
- def setting_time(latitude:, longitude:, date:)
43
- h2_component = h2(latitude: latitude)
44
- return nil if h2_component.nil?
45
-
46
- setting_lst = @equatorial_coordinates.right_ascension.hours + h2_component.degrees
47
- setting_lst -= 24 if setting_lst > 24
48
-
49
- Util::Time.lst_to_ut(date: date, longitude: longitude, lst: setting_lst)
64
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
65
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
66
+ # Edition: Cambridge University Press
67
+ # Chapter: 33 - Rising and setting
68
+
69
+ # @param latitude [Astronoby::Angle] Latitude of the observer
70
+ # @param longitude [Astronoby::Angle] Longitude of the observer
71
+ # @param date [Date] Date of the event
72
+ # @param apparent [Boolean] Compute apparent or true data
73
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
74
+ # @return [Time, nil] Sunset time
75
+ def setting_time(
76
+ latitude:,
77
+ longitude:,
78
+ date:,
79
+ apparent: true,
80
+ vertical_shift: nil
81
+ )
82
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
83
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
84
+
85
+ hour_angle = Angle.acos(time_ratio)
86
+ local_sidereal_time = LocalSiderealTime.new(
87
+ date: date,
88
+ time: right_ascension.hours + hour_angle.hours,
89
+ longitude: longitude
90
+ )
91
+
92
+ local_sidereal_time.to_gst.to_utc
50
93
  end
51
94
 
52
95
  # Source:
53
- # Title: Celestial Calculations
54
- # Author: J. L. Lawrence
55
- # Edition: MIT Press
56
- # Chapter: 5 - Stars in the Nighttime Sky
57
- def setting_azimuth(latitude:)
58
- rising_az = rising_azimuth(latitude: latitude)
59
- return nil if rising_az.nil?
60
-
61
- Angle.as_degrees(360 - rising_az.degrees)
96
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
97
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
98
+ # Edition: Cambridge University Press
99
+ # Chapter: 33 - Rising and setting
100
+
101
+ # @param latitude [Astronoby::Angle] Latitude of the observer
102
+ # @param apparent [Boolean] Compute apparent or true data
103
+ # @param vertical_shift [Astronoby::Angle] Vertical shift correction angle
104
+ # @return [Astronoby::Angle, nil] Sunset azimuth
105
+ def setting_azimuth(latitude:, apparent: true, vertical_shift: nil)
106
+ time_ratio = time_ratio(latitude, apparent, vertical_shift)
107
+ return nil unless RISING_SETTING_HOUR_ANGLE_RATIO_RANGE.cover?(time_ratio)
108
+
109
+ azimuth_ratio = azimuth_ratio(latitude, apparent, vertical_shift)
110
+
111
+ Angle.as_degrees(360 - Angle.acos(azimuth_ratio).degrees)
62
112
  end
63
113
 
64
114
  private
65
115
 
66
- def azimuth_component(latitude:)
67
- @equatorial_coordinates.declination.sin / latitude.cos
116
+ def time_ratio(latitude, apparent, vertical_shift)
117
+ shift = if vertical_shift
118
+ vertical_shift
119
+ elsif apparent
120
+ DEFAULT_REFRACTION_VERTICAL_SHIFT
121
+ else
122
+ Angle.zero
123
+ end
124
+
125
+ term1 = shift.sin + latitude.sin * declination.sin
126
+ term2 = latitude.cos * declination.cos
127
+
128
+ -term1 / term2
129
+ end
130
+
131
+ def azimuth_ratio(latitude, apparent, vertical_shift)
132
+ shift = if vertical_shift
133
+ vertical_shift
134
+ elsif apparent
135
+ DEFAULT_REFRACTION_VERTICAL_SHIFT
136
+ else
137
+ Angle.zero
138
+ end
139
+
140
+ (declination.sin + shift.sin * latitude.cos) / (shift.cos * latitude.cos)
68
141
  end
69
142
 
70
- def h2(latitude:)
71
- ar = azimuth_component(latitude: latitude)
72
- return nil if ar >= 1
143
+ def azimuth_component(latitude)
144
+ declination.sin / latitude.cos
145
+ end
73
146
 
74
- h1 = latitude.tan * @equatorial_coordinates.declination.tan
75
- return nil if h1.abs > 1
147
+ def right_ascension
148
+ @equatorial_coordinates.right_ascension
149
+ end
76
150
 
77
- Angle.as_radians(Math.acos(-h1) / 15.0)
151
+ def declination
152
+ @equatorial_coordinates.declination
78
153
  end
79
154
  end
80
155
  end
@@ -18,12 +18,11 @@ module Astronoby
18
18
  end
19
19
 
20
20
  def compute_hour_angle(time:, longitude:)
21
- lst = Util::Time.local_sidereal_time(
22
- time: time,
23
- longitude: longitude
24
- )
21
+ lst = GreenwichSiderealTime
22
+ .from_utc(time.utc)
23
+ .to_lst(longitude: longitude)
25
24
 
26
- ha = (lst - @right_ascension.hours)
25
+ ha = (lst.time - @right_ascension.hours)
27
26
  ha += 24 if ha.negative?
28
27
 
29
28
  Angle.as_hours(ha)
@@ -36,10 +36,10 @@ module Astronoby
36
36
  end
37
37
 
38
38
  hour_angle_hours = Angle.as_degrees(hour_angle_degrees).hours
39
- right_ascension_decimal = Util::Time.local_sidereal_time(
40
- time: time,
41
- longitude: @longitude
42
- ) - hour_angle_hours
39
+ lst = GreenwichSiderealTime
40
+ .from_utc(time.utc)
41
+ .to_lst(longitude: @longitude)
42
+ right_ascension_decimal = lst.time - hour_angle_hours
43
43
  right_ascension_decimal += 24 if right_ascension_decimal.negative?
44
44
  right_ascension = Angle.as_hours(right_ascension_decimal)
45
45
 
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class EquinoxSolstice
5
+ # Source:
6
+ # Title: Astronomical Algorithms
7
+ # Author: Jean Meeus
8
+ # Edition: 2nd edition
9
+ # Chapter: 27 - Equinoxes and Soltices
10
+
11
+ EVENTS = [
12
+ MARCH_EQUINOX = 0,
13
+ JUNE_SOLSTICE = 1,
14
+ SEPTEMBER_EQUINOX = 2,
15
+ DECEMBER_SOLSTICE = 3
16
+ ].freeze
17
+
18
+ JDE_COMPONENTS = {
19
+ MARCH_EQUINOX => [
20
+ 2451623.80984,
21
+ 365242.37404,
22
+ 0.05169,
23
+ -0.00411,
24
+ -0.00057
25
+ ],
26
+ JUNE_SOLSTICE => [
27
+ 2451716.56767,
28
+ 365241.62603,
29
+ 0.00325,
30
+ 0.00888,
31
+ -0.00030
32
+ ],
33
+ SEPTEMBER_EQUINOX => [
34
+ 2451810.21715,
35
+ 365242.01767,
36
+ -0.11575,
37
+ 0.00337,
38
+ 0.00078
39
+ ],
40
+ DECEMBER_SOLSTICE => [
41
+ 2451900.05952,
42
+ 365242.74049,
43
+ -0.06223,
44
+ -0.00823,
45
+ 0.00032
46
+ ]
47
+ }
48
+
49
+ PERIODIC_TERMS = [
50
+ [485, 324.96, 1934.136],
51
+ [203, 337.23, 32964.467],
52
+ [199, 342.08, 20.186],
53
+ [182, 27.85, 445267.112],
54
+ [156, 73.14, 45036.886],
55
+ [136, 171.52, 22518.443],
56
+ [77, 222.54, 65928.934],
57
+ [74, 296.72, 3034.906],
58
+ [70, 243.58, 9037.513],
59
+ [58, 119.81, 33718.147],
60
+ [52, 297.17, 150.678],
61
+ [50, 21.02, 2281.226],
62
+ [45, 247.54, 29929.562],
63
+ [44, 325.15, 31555.956],
64
+ [29, 60.93, 4443.417],
65
+ [18, 155.12, 67555.328],
66
+ [17, 288.79, 4562.452],
67
+ [16, 198.04, 62894.029],
68
+ [14, 199.76, 31436.921],
69
+ [12, 95.39, 14577.848],
70
+ [12, 287.11, 31931.756],
71
+ [12, 320.81, 34777.259],
72
+ [9, 227.73, 1222.114],
73
+ [8, 15.45, 16859.074]
74
+ ].freeze
75
+
76
+ def self.march_equinox(year)
77
+ new(year, MARCH_EQUINOX).compute
78
+ end
79
+
80
+ def self.june_solstice(year)
81
+ new(year, JUNE_SOLSTICE).compute
82
+ end
83
+
84
+ def self.september_equinox(year)
85
+ new(year, SEPTEMBER_EQUINOX).compute
86
+ end
87
+
88
+ def self.december_solstice(year)
89
+ new(year, DECEMBER_SOLSTICE).compute
90
+ end
91
+
92
+ def initialize(year, event)
93
+ unless EVENTS.include?(event)
94
+ raise UnsupportedEventError.new(
95
+ "Expected a format between #{EVENTS.join(", ")}, got #{event}"
96
+ )
97
+ end
98
+
99
+ @event = event
100
+ @year = (year.to_i - 2000) / 1000.0
101
+ end
102
+
103
+ def compute
104
+ t = (julian_day - Epoch::J2000) / Epoch::DAYS_PER_JULIAN_CENTURY
105
+ w = Angle.as_degrees(35999.373 * t) - Angle.as_degrees(2.47)
106
+ delta = 1 +
107
+ 0.0334 * w.cos +
108
+ 0.0007 * Angle.as_degrees(w.degrees * 2).cos
109
+
110
+ s = PERIODIC_TERMS.sum do |a, b, c|
111
+ a * (Angle.as_degrees(b) + Angle.as_degrees(c * t)).cos
112
+ end
113
+
114
+ delta_days = 0.00001 * s / delta
115
+ epoch = julian_day + delta_days
116
+ epoch += correction(epoch)
117
+
118
+ Epoch.to_utc(epoch).round
119
+ end
120
+
121
+ private
122
+
123
+ def julian_day
124
+ component = JDE_COMPONENTS[@event]
125
+ component[0] +
126
+ component[1] * @year +
127
+ component[2] * @year**2 +
128
+ component[3] * @year**3 +
129
+ component[4] * @year**4
130
+ end
131
+
132
+ def correction(epoch)
133
+ sun = Sun.new(epoch: epoch)
134
+
135
+ nutation = Nutation.for_ecliptic_longitude(epoch: epoch)
136
+
137
+ earth_radius_vector = 1 / (
138
+ 1 +
139
+ sun.orbital_eccentricity.degrees *
140
+ (sun.true_anomaly - sun.longitude_at_perigee).cos
141
+ )
142
+ aberration = Angle.as_degrees(
143
+ Angle.as_dms(0, 0, 20.4898).degrees / -earth_radius_vector
144
+ )
145
+
146
+ corrected_longitude = sun.ecliptic_coordinates.longitude +
147
+ nutation +
148
+ aberration
149
+
150
+ 58 * Angle.as_degrees(@event * 90 - corrected_longitude.degrees).sin
151
+ end
152
+ end
153
+ end
@@ -2,4 +2,6 @@ module Astronoby
2
2
  class IncompatibleArgumentsError < ArgumentError; end
3
3
 
4
4
  class UnsupportedFormatError < ArgumentError; end
5
+
6
+ class UnsupportedEventError < ArgumentError; end
5
7
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class GeocentricParallax
5
+ # Source:
6
+ # Title: Practical Astronomy with your Calculator or Spreadsheet
7
+ # Authors: Peter Duffett-Smith and Jonathan Zwart
8
+ # Edition: Cambridge University Press
9
+ # Chapter: 39 - Calculating correction for parallax
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
+ # Equatorial horizontal parallax
16
+ # @param distance [Numeric] Distance of the body from the center of the
17
+ # Earth, in meters
18
+ # @return [Astronoby::Angle] Equatorial horizontal parallax angle
19
+ def self.angle(distance:)
20
+ distance_in_earth_radius = distance / EARTH_EQUATORIAL_RADIUS
21
+ Angle.asin(1 / distance_in_earth_radius)
22
+ end
23
+
24
+ # 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
28
+ # @param time [Time] Date-time of the observation
29
+ # @param coordinates [Astronoby::Coordinates::Equatorial]
30
+ # Equatorial coordinates of the observed body
31
+ # @param distance [Numeric] Distance of the observed body from the center of
32
+ # the Earth, in meters
33
+ # @return [Astronoby::Coordinates::Equatorial] Apparent equatorial
34
+ # coordinates with equatorial horizontal parallax
35
+ def self.for_equatorial_coordinates(
36
+ latitude:,
37
+ longitude:,
38
+ elevation:,
39
+ time:,
40
+ coordinates:,
41
+ distance:
42
+ )
43
+ new(
44
+ latitude,
45
+ longitude,
46
+ elevation,
47
+ time,
48
+ coordinates,
49
+ distance
50
+ ).apply
51
+ end
52
+
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
56
+ # @param time [Time] Date-time of the observation
57
+ # @param coordinates [Astronoby::Coordinates::Equatorial] Equatorial
58
+ # coordinates of the observed body
59
+ # @param distance [Numeric] Distance of the observed body from the center of
60
+ # the Earth, in meters
61
+ def initialize(
62
+ latitude,
63
+ longitude,
64
+ elevation,
65
+ time,
66
+ coordinates,
67
+ distance
68
+ )
69
+ @latitude = latitude
70
+ @longitude = longitude
71
+ @elevation = elevation
72
+ @time = time
73
+ @coordinates = coordinates
74
+ @distance = distance
75
+ end
76
+
77
+ 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
86
+
87
+ delta = Angle.atan(term2 / term3)
88
+
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
+ )
99
+
100
+ Coordinates::Equatorial.new(
101
+ right_ascension: apparent_right_ascension,
102
+ declination: apparent_declination,
103
+ epoch: @coordinates.epoch
104
+ )
105
+ end
106
+
107
+ private
108
+
109
+ def right_ascension
110
+ @coordinates.right_ascension
111
+ end
112
+
113
+ def declination
114
+ @coordinates.declination
115
+ end
116
+
117
+ def hour_angle
118
+ @_hour_angle ||=
119
+ @coordinates.compute_hour_angle(time: @time, longitude: @longitude)
120
+ end
121
+
122
+ def elevation_ratio
123
+ @elevation / EARTH_EQUATORIAL_RADIUS
124
+ end
125
+
126
+ def distance_in_earth_radius
127
+ @distance / EARTH_EQUATORIAL_RADIUS
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
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")
12
+
13
+ attr_reader :latitude, :longitude, :elevation, :temperature
14
+
15
+ # @param latitude [Angle] geographic latitude of the observer
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
19
+ # @param temperature [Numeric] temperature at the observer's location in
20
+ # kelvins
21
+ # @param pressure [Numeric] atmospheric pressure at the observer's
22
+ # location in millibars
23
+ def initialize(
24
+ latitude:,
25
+ longitude:,
26
+ elevation: DEFAULT_ELEVATION,
27
+ temperature: DEFAULT_TEMPERATURE,
28
+ pressure: nil
29
+ )
30
+ @latitude = latitude
31
+ @longitude = longitude
32
+ @elevation = elevation
33
+ @temperature = temperature
34
+ @pressure = pressure
35
+ end
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
43
+ end
44
+
45
+ private
46
+
47
+ # Source:
48
+ # Barometric formula
49
+ # https://en.wikipedia.org/wiki/Barometric_formula
50
+ def pressure_ratio
51
+ term1 = EARTH_GRAVITATIONAL_ACCELERATION * MOLAR_MASS_OF_AIR * @elevation
52
+ term2 = UNIVERSAL_GAS_CONSTANT * @temperature
53
+
54
+ Math.exp(-term1 / term2)
55
+ end
56
+ end
57
+ end
@@ -2,21 +2,20 @@
2
2
 
3
3
  module Astronoby
4
4
  class Refraction
5
- DEFAULT_PRESSURE = 1000
6
- DEFAULT_TEMPERATURE = 25
5
+ LOW_ALTITUDE_BODY_ANGLE = Angle.as_degrees(15)
6
+ ZENITH = Angle.as_degrees(90)
7
7
 
8
- def self.for_horizontal_coordinates(
9
- coordinates:,
10
- pressure: DEFAULT_PRESSURE,
11
- temperature: DEFAULT_TEMPERATURE
12
- )
13
- new(coordinates, pressure, temperature).refract
8
+ def self.angle(coordinates:, observer:)
9
+ new(coordinates, observer).refraction_angle
14
10
  end
15
11
 
16
- def initialize(coordinates, pressure, temperature)
12
+ def self.correct_horizontal_coordinates(coordinates:, observer:)
13
+ new(coordinates, observer).refract
14
+ end
15
+
16
+ def initialize(coordinates, observer)
17
17
  @coordinates = coordinates
18
- @pressure = pressure
19
- @temperature = temperature
18
+ @observer = observer
20
19
  end
21
20
 
22
21
  # Source:
@@ -25,31 +24,6 @@ module Astronoby
25
24
  # Edition: Cambridge University Press
26
25
  # Chapter: 37 - Refraction
27
26
  def refract
28
- altitude_in_degrees = @coordinates.altitude.degrees
29
-
30
- refraction_angle = Angle.as_degrees(
31
- if altitude_in_degrees > 15
32
- zenith_angle = Angle.as_degrees(90 - @coordinates.altitude.degrees)
33
- 0.00452 * @pressure * zenith_angle.tan / (273 + @temperature)
34
- else
35
- (
36
- @pressure *
37
- (
38
- 0.1594 +
39
- 0.0196 * altitude_in_degrees +
40
- 0.00002 * altitude_in_degrees * altitude_in_degrees
41
- )
42
- )./(
43
- (273 + @temperature) *
44
- (
45
- 1 +
46
- 0.505 * altitude_in_degrees +
47
- 0.0845 * altitude_in_degrees * altitude_in_degrees
48
- )
49
- )
50
- end
51
- )
52
-
53
27
  Coordinates::Horizontal.new(
54
28
  azimuth: @coordinates.azimuth,
55
29
  altitude: @coordinates.altitude + refraction_angle,
@@ -57,5 +31,43 @@ module Astronoby
57
31
  longitude: @coordinates.longitude
58
32
  )
59
33
  end
34
+
35
+ def refraction_angle
36
+ if @coordinates.altitude > LOW_ALTITUDE_BODY_ANGLE
37
+ high_altitude_angle
38
+ else
39
+ low_altitude_angle
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def pressure
46
+ @_pressure ||= @observer.pressure
47
+ end
48
+
49
+ def temperature
50
+ @_temperature ||= @observer.temperature
51
+ end
52
+
53
+ def altitude_in_degrees
54
+ @_altitude_in_degrees ||= @coordinates.altitude.degrees
55
+ end
56
+
57
+ def high_altitude_angle
58
+ zenith_angle = ZENITH - @coordinates.altitude
59
+ Angle.as_degrees(0.00452 * pressure * zenith_angle.tan / temperature)
60
+ end
61
+
62
+ def low_altitude_angle
63
+ term1 = pressure * (
64
+ 0.1594 + 0.0196 * altitude_in_degrees + 0.00002 * altitude_in_degrees**2
65
+ )
66
+ term2 = temperature * (
67
+ 1 + 0.505 * altitude_in_degrees + 0.0845 * altitude_in_degrees**2
68
+ )
69
+
70
+ Angle.as_degrees(term1 / term2)
71
+ end
60
72
  end
61
73
  end