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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/Gemfile.lock +38 -36
- data/README.md +17 -2
- data/UPGRADING.md +109 -0
- data/lib/astronoby/angle.rb +2 -0
- data/lib/astronoby/bodies/sun.rb +157 -12
- data/lib/astronoby/body.rb +126 -51
- data/lib/astronoby/coordinates/equatorial.rb +4 -5
- data/lib/astronoby/coordinates/horizontal.rb +4 -4
- data/lib/astronoby/equinox_solstice.rb +153 -0
- data/lib/astronoby/errors.rb +2 -0
- data/lib/astronoby/geocentric_parallax.rb +130 -0
- data/lib/astronoby/observer.rb +57 -0
- data/lib/astronoby/refraction.rb +48 -36
- data/lib/astronoby/time/greenwich_sidereal_time.rb +86 -0
- data/lib/astronoby/time/local_sidereal_time.rb +41 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +5 -1
- metadata +8 -3
- data/lib/astronoby/util/time.rb +0 -93
data/lib/astronoby/body.rb
CHANGED
|
@@ -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:
|
|
11
|
-
#
|
|
12
|
-
# Edition:
|
|
13
|
-
# Chapter:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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:
|
|
27
|
-
#
|
|
28
|
-
# Edition:
|
|
29
|
-
# Chapter:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
39
|
-
#
|
|
40
|
-
# Edition:
|
|
41
|
-
# Chapter:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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:
|
|
54
|
-
#
|
|
55
|
-
# Edition:
|
|
56
|
-
# Chapter:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
67
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
143
|
+
def azimuth_component(latitude)
|
|
144
|
+
declination.sin / latitude.cos
|
|
145
|
+
end
|
|
73
146
|
|
|
74
|
-
|
|
75
|
-
|
|
147
|
+
def right_ascension
|
|
148
|
+
@equatorial_coordinates.right_ascension
|
|
149
|
+
end
|
|
76
150
|
|
|
77
|
-
|
|
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 =
|
|
22
|
-
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
|
-
|
|
40
|
-
time
|
|
41
|
-
longitude: @longitude
|
|
42
|
-
|
|
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
|
data/lib/astronoby/errors.rb
CHANGED
|
@@ -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
|
data/lib/astronoby/refraction.rb
CHANGED
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
4
|
class Refraction
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
LOW_ALTITUDE_BODY_ANGLE = Angle.as_degrees(15)
|
|
6
|
+
ZENITH = Angle.as_degrees(90)
|
|
7
7
|
|
|
8
|
-
def self.
|
|
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
|
|
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
|
-
@
|
|
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
|