astronoby 0.0.1 → 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/.tool-versions +1 -0
- data/CHANGELOG.md +74 -4
- data/CONTRIBUTING.md +86 -0
- data/Gemfile.lock +52 -35
- data/README.md +81 -9
- data/UPGRADING.md +109 -0
- data/lib/astronoby/aberration.rb +42 -0
- data/lib/astronoby/angle.rb +157 -21
- data/lib/astronoby/angles/dms.rb +18 -0
- data/lib/astronoby/angles/hms.rb +17 -0
- data/lib/astronoby/bodies/sun.rb +226 -0
- data/lib/astronoby/body.rb +155 -0
- data/lib/astronoby/coordinates/ecliptic.rb +42 -0
- data/lib/astronoby/coordinates/equatorial.rb +88 -0
- data/lib/astronoby/coordinates/horizontal.rb +53 -0
- data/lib/astronoby/epoch.rb +24 -0
- data/lib/astronoby/equinox_solstice.rb +153 -0
- data/lib/astronoby/errors.rb +7 -0
- data/lib/astronoby/geocentric_parallax.rb +130 -0
- data/lib/astronoby/mean_obliquity.rb +32 -0
- data/lib/astronoby/nutation.rb +71 -0
- data/lib/astronoby/observer.rb +57 -0
- data/lib/astronoby/precession.rb +86 -0
- data/lib/astronoby/refraction.rb +73 -0
- data/lib/astronoby/time/greenwich_sidereal_time.rb +86 -0
- data/lib/astronoby/time/local_sidereal_time.rb +41 -0
- data/lib/astronoby/true_obliquity.rb +12 -0
- data/lib/astronoby/util/astrodynamics.rb +60 -0
- data/lib/astronoby/util/trigonometry.rb +26 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +23 -8
- metadata +45 -25
- data/.prettierrc +0 -11
- data/.standard.yml +0 -3
- data/astronoby.gemspec +0 -40
- data/lib/astronoby/angles/degree.rb +0 -17
- data/lib/astronoby/angles/radian.rb +0 -17
- data/sig/astronoby.rbs +0 -4
data/lib/astronoby/angle.rb
CHANGED
@@ -4,39 +4,175 @@ require "bigdecimal/math"
|
|
4
4
|
|
5
5
|
module Astronoby
|
6
6
|
class Angle
|
7
|
-
|
8
|
-
DEGREES = :degrees,
|
9
|
-
RADIANS = :radians
|
10
|
-
].freeze
|
7
|
+
include Comparable
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
9
|
+
PRECISION = 14
|
10
|
+
PI = BigMath.PI(PRECISION)
|
11
|
+
PI_IN_DEGREES = BigDecimal("180")
|
16
12
|
|
17
|
-
|
13
|
+
FULL_CIRCLE_IN_RADIANS = (2 * PI)
|
14
|
+
|
15
|
+
RADIAN_PER_HOUR = PI / BigDecimal("12")
|
16
|
+
MINUTES_PER_DEGREE = BigDecimal("60")
|
17
|
+
MINUTES_PER_HOUR = BigDecimal("60")
|
18
|
+
SECONDS_PER_MINUTE = BigDecimal("60")
|
19
|
+
SECONDS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE
|
20
|
+
|
21
|
+
FORMATS = %i[dms hms].freeze
|
18
22
|
|
19
23
|
class << self
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
def zero
|
25
|
+
new(0)
|
26
|
+
end
|
27
|
+
|
28
|
+
def as_radians(radians)
|
29
|
+
normalized_radians = radians.remainder(FULL_CIRCLE_IN_RADIANS)
|
30
|
+
new(normalized_radians)
|
31
|
+
end
|
32
|
+
|
33
|
+
def as_degrees(degrees)
|
34
|
+
radians = degrees / PI_IN_DEGREES * PI
|
35
|
+
as_radians(radians)
|
36
|
+
end
|
37
|
+
|
38
|
+
def as_hours(hours)
|
39
|
+
radians = hours * RADIAN_PER_HOUR
|
40
|
+
as_radians(radians)
|
41
|
+
end
|
42
|
+
|
43
|
+
def as_hms(hour, minute, second)
|
44
|
+
hours = hour + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR
|
45
|
+
as_hours(hours)
|
46
|
+
end
|
47
|
+
|
48
|
+
def as_dms(degree, minute, second)
|
49
|
+
sign = degree.negative? ? -1 : 1
|
50
|
+
degrees = degree.abs + minute / MINUTES_PER_HOUR + second / SECONDS_PER_HOUR
|
51
|
+
as_degrees(sign * degrees)
|
52
|
+
end
|
53
|
+
|
54
|
+
def asin(ratio)
|
55
|
+
radians = Math.asin(ratio)
|
56
|
+
as_radians(radians)
|
57
|
+
end
|
58
|
+
|
59
|
+
def acos(ratio)
|
60
|
+
radians = Math.acos(ratio)
|
61
|
+
as_radians(radians)
|
62
|
+
end
|
63
|
+
|
64
|
+
def atan(ratio)
|
65
|
+
radians = Math.atan(ratio)
|
66
|
+
as_radians(radians)
|
24
67
|
end
|
25
68
|
end
|
26
69
|
|
27
|
-
|
28
|
-
|
29
|
-
|
70
|
+
attr_reader :radians
|
71
|
+
|
72
|
+
def initialize(radians)
|
73
|
+
@radians = if radians.is_a?(Integer) || radians.is_a?(BigDecimal)
|
74
|
+
BigDecimal(radians)
|
75
|
+
else
|
76
|
+
BigDecimal(radians, PRECISION)
|
30
77
|
end
|
78
|
+
freeze
|
31
79
|
end
|
32
80
|
|
33
|
-
def
|
34
|
-
@
|
35
|
-
@unit = unit
|
81
|
+
def degrees
|
82
|
+
@radians * PI_IN_DEGREES / PI
|
36
83
|
end
|
37
84
|
|
38
|
-
def
|
39
|
-
@
|
85
|
+
def hours
|
86
|
+
@radians / RADIAN_PER_HOUR
|
87
|
+
end
|
88
|
+
|
89
|
+
def +(other)
|
90
|
+
self.class.as_radians(radians + other.radians)
|
91
|
+
end
|
92
|
+
|
93
|
+
def -(other)
|
94
|
+
self.class.as_radians(@radians - other.radians)
|
95
|
+
end
|
96
|
+
|
97
|
+
def sin
|
98
|
+
Math.sin(radians)
|
99
|
+
end
|
100
|
+
|
101
|
+
def cos
|
102
|
+
Math.cos(radians)
|
103
|
+
end
|
104
|
+
|
105
|
+
def tan
|
106
|
+
Math.tan(radians)
|
107
|
+
end
|
108
|
+
|
109
|
+
def positive?
|
110
|
+
radians > 0
|
111
|
+
end
|
112
|
+
|
113
|
+
def negative?
|
114
|
+
radians < 0
|
115
|
+
end
|
116
|
+
|
117
|
+
def zero?
|
118
|
+
radians.zero?
|
119
|
+
end
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
other.is_a?(self.class) && radians == other.radians
|
123
|
+
end
|
124
|
+
alias_method :eql?, :==
|
125
|
+
|
126
|
+
def hash
|
127
|
+
[radians, self.class].hash
|
128
|
+
end
|
129
|
+
|
130
|
+
def <=>(other)
|
131
|
+
return nil unless other.is_a?(self.class)
|
132
|
+
|
133
|
+
radians <=> other.radians
|
134
|
+
end
|
135
|
+
|
136
|
+
def str(format)
|
137
|
+
case format
|
138
|
+
when :dms then to_dms(degrees).format
|
139
|
+
when :hms then to_hms(hours).format
|
140
|
+
else
|
141
|
+
raise UnsupportedFormatError.new(
|
142
|
+
"Expected a format between #{FORMATS.join(", ")}, got #{format}"
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_dms(deg)
|
148
|
+
sign = deg.negative? ? "-" : "+"
|
149
|
+
absolute_degrees = deg.abs
|
150
|
+
degrees = absolute_degrees.floor
|
151
|
+
decimal_minutes = MINUTES_PER_DEGREE * (absolute_degrees - degrees)
|
152
|
+
absolute_decimal_minutes = (
|
153
|
+
MINUTES_PER_DEGREE * (absolute_degrees - degrees)
|
154
|
+
).abs
|
155
|
+
minutes = decimal_minutes.floor
|
156
|
+
seconds = SECONDS_PER_MINUTE * (
|
157
|
+
absolute_decimal_minutes - absolute_decimal_minutes.floor
|
158
|
+
)
|
159
|
+
|
160
|
+
Dms.new(sign, degrees, minutes, seconds.to_f.floor(4))
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_hms(hrs)
|
164
|
+
absolute_hours = hrs.abs
|
165
|
+
hours = absolute_hours.floor
|
166
|
+
decimal_minutes = MINUTES_PER_HOUR * (absolute_hours - hours)
|
167
|
+
absolute_decimal_minutes = (
|
168
|
+
MINUTES_PER_HOUR * (absolute_hours - hours)
|
169
|
+
).abs
|
170
|
+
minutes = decimal_minutes.floor
|
171
|
+
seconds = SECONDS_PER_MINUTE * (
|
172
|
+
absolute_decimal_minutes - absolute_decimal_minutes.floor
|
173
|
+
)
|
174
|
+
|
175
|
+
Hms.new(hours, minutes, seconds.to_f.floor(4))
|
40
176
|
end
|
41
177
|
end
|
42
178
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Dms
|
5
|
+
attr_reader :sign, :degrees, :minutes, :seconds
|
6
|
+
|
7
|
+
def initialize(sign, degrees, minutes, seconds)
|
8
|
+
@sign = sign
|
9
|
+
@degrees = degrees
|
10
|
+
@minutes = minutes
|
11
|
+
@seconds = seconds
|
12
|
+
end
|
13
|
+
|
14
|
+
def format
|
15
|
+
"#{sign}#{degrees}° #{minutes}′ #{seconds}″"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Hms
|
5
|
+
attr_reader :hours, :minutes, :seconds
|
6
|
+
|
7
|
+
def initialize(hours, minutes, seconds)
|
8
|
+
@hours = hours
|
9
|
+
@minutes = minutes
|
10
|
+
@seconds = seconds
|
11
|
+
end
|
12
|
+
|
13
|
+
def format
|
14
|
+
"#{hours}h #{minutes}m #{seconds}s"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Sun
|
5
|
+
SEMI_MAJOR_AXIS_IN_METERS = 149_598_500_000
|
6
|
+
ANGULAR_DIAMETER = Angle.as_degrees(0.533128)
|
7
|
+
INTERPOLATION_FACTOR = BigDecimal("24.07")
|
8
|
+
|
9
|
+
# Source:
|
10
|
+
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
11
|
+
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
12
|
+
# Edition: Cambridge University Press
|
13
|
+
# Chapter: 51 - The equation of time
|
14
|
+
|
15
|
+
# @param date [Date] Requested date
|
16
|
+
# @return [Integer] Equation of time in seconds
|
17
|
+
def self.equation_of_time(date:)
|
18
|
+
noon = Time.utc(date.year, date.month, date.day, 12)
|
19
|
+
epoch_at_noon = Epoch.from_time(noon)
|
20
|
+
sun_at_noon = new(epoch: epoch_at_noon)
|
21
|
+
equatorial_hours = sun_at_noon
|
22
|
+
.ecliptic_coordinates
|
23
|
+
.to_equatorial(epoch: epoch_at_noon)
|
24
|
+
.right_ascension
|
25
|
+
.hours
|
26
|
+
gst = GreenwichSiderealTime
|
27
|
+
.new(date: date, time: equatorial_hours)
|
28
|
+
.to_utc
|
29
|
+
|
30
|
+
(noon - gst).to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
# Source:
|
34
|
+
# Title: Celestial Calculations
|
35
|
+
# Author: J. L. Lawrence
|
36
|
+
# Edition: MIT Press
|
37
|
+
# Chapter: 6 - The Sun
|
38
|
+
|
39
|
+
# @param epoch [Numeric] Considered epoch, in Julian days
|
40
|
+
def initialize(epoch:)
|
41
|
+
@epoch = epoch
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Astronoby::Coordinates::Ecliptic] Sun's ecliptic coordinates
|
45
|
+
def ecliptic_coordinates
|
46
|
+
Coordinates::Ecliptic.new(
|
47
|
+
latitude: Angle.zero,
|
48
|
+
longitude: Angle.as_degrees(
|
49
|
+
(true_anomaly + longitude_at_perigee).degrees % 360
|
50
|
+
)
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Computes the Sun's horizontal coordinates
|
55
|
+
#
|
56
|
+
# @param latitude [Astronoby::Angle] Latitude of the observer
|
57
|
+
# @param longitude [Astronoby::Angle] Longitude of the observer
|
58
|
+
# @return [Astronoby::Coordinates::Horizontal] Sun's horizontal coordinates
|
59
|
+
def horizontal_coordinates(latitude:, longitude:)
|
60
|
+
time = Epoch.to_utc(@epoch)
|
61
|
+
|
62
|
+
ecliptic_coordinates
|
63
|
+
.to_equatorial(epoch: @epoch)
|
64
|
+
.to_horizontal(time: time, latitude: latitude, longitude: longitude)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param observer [Astronoby::Observer] Observer of the event
|
68
|
+
# @return [Time] Time of sunrise
|
69
|
+
def rising_time(observer:)
|
70
|
+
event_date = Epoch.to_utc(@epoch).to_date
|
71
|
+
lst1 = event_local_sidereal_time_for_date(event_date, observer, :rising)
|
72
|
+
next_day = event_date.next_day(1)
|
73
|
+
lst2 = event_local_sidereal_time_for_date(next_day, observer, :rising)
|
74
|
+
time = (INTERPOLATION_FACTOR * lst1) / (INTERPOLATION_FACTOR + lst1 - lst2)
|
75
|
+
|
76
|
+
LocalSiderealTime.new(
|
77
|
+
date: event_date,
|
78
|
+
time: time,
|
79
|
+
longitude: observer.longitude
|
80
|
+
).to_gst.to_utc
|
81
|
+
end
|
82
|
+
|
83
|
+
# @param observer [Astronoby::Observer] Observer of the event
|
84
|
+
# @return [Astronoby::Angle, nil] Azimuth of sunrise
|
85
|
+
def rising_azimuth(observer:)
|
86
|
+
equatorial_coordinates = ecliptic_coordinates.to_equatorial(epoch: @epoch)
|
87
|
+
Body.new(equatorial_coordinates).rising_azimuth(
|
88
|
+
latitude: observer.latitude,
|
89
|
+
vertical_shift: vertical_shift
|
90
|
+
)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @param observer [Astronoby::Observer] Observer of the event
|
94
|
+
# @return [Time] Time of sunset
|
95
|
+
def setting_time(observer:)
|
96
|
+
event_date = Epoch.to_utc(@epoch).to_date
|
97
|
+
lst1 = event_local_sidereal_time_for_date(event_date, observer, :setting)
|
98
|
+
next_day = event_date.next_day(1)
|
99
|
+
lst2 = event_local_sidereal_time_for_date(next_day, observer, :setting)
|
100
|
+
time = (INTERPOLATION_FACTOR * lst1) / (INTERPOLATION_FACTOR + lst1 - lst2)
|
101
|
+
|
102
|
+
LocalSiderealTime.new(
|
103
|
+
date: event_date,
|
104
|
+
time: time,
|
105
|
+
longitude: observer.longitude
|
106
|
+
).to_gst.to_utc
|
107
|
+
end
|
108
|
+
|
109
|
+
# @param observer [Astronoby::Observer] Observer of the event
|
110
|
+
# @return [Astronoby::Angle, nil] Azimuth of sunset
|
111
|
+
def setting_azimuth(observer:)
|
112
|
+
equatorial_coordinates = ecliptic_coordinates.to_equatorial(epoch: @epoch)
|
113
|
+
Body.new(equatorial_coordinates).setting_azimuth(
|
114
|
+
latitude: observer.latitude,
|
115
|
+
vertical_shift: vertical_shift
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [Numeric] Earth-Sun distance in meters
|
120
|
+
def earth_distance
|
121
|
+
SEMI_MAJOR_AXIS_IN_METERS / distance_angular_size_factor
|
122
|
+
end
|
123
|
+
|
124
|
+
# @return [Astronoby::Angle] Apparent Sun's angular size
|
125
|
+
def angular_size
|
126
|
+
Angle.as_degrees(ANGULAR_DIAMETER.degrees * distance_angular_size_factor)
|
127
|
+
end
|
128
|
+
|
129
|
+
# @return [Astronoby::Angle] Sun's true anomaly
|
130
|
+
def true_anomaly
|
131
|
+
eccentric_anomaly = Util::Astrodynamics.eccentric_anomaly_newton_raphson(
|
132
|
+
mean_anomaly,
|
133
|
+
orbital_eccentricity.degrees,
|
134
|
+
2e-06,
|
135
|
+
10
|
136
|
+
)
|
137
|
+
|
138
|
+
tan = Math.sqrt(
|
139
|
+
(1 + orbital_eccentricity.degrees) / (1 - orbital_eccentricity.degrees)
|
140
|
+
) * Math.tan(eccentric_anomaly.radians / 2)
|
141
|
+
|
142
|
+
Angle.as_degrees((Angle.atan(tan).degrees * 2) % 360)
|
143
|
+
end
|
144
|
+
|
145
|
+
# @return [Astronoby::Angle] Sun's longitude at perigee
|
146
|
+
def longitude_at_perigee
|
147
|
+
Angle.as_degrees(
|
148
|
+
(281.2208444 + 1.719175 * centuries + 0.000452778 * centuries**2) % 360
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
# @return [Astronoby::Angle] Sun's orbital eccentricity
|
153
|
+
def orbital_eccentricity
|
154
|
+
Angle.as_degrees(
|
155
|
+
(0.01675104 - 0.0000418 * centuries - 0.000000126 * centuries**2) % 360
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def mean_anomaly
|
162
|
+
Angle.as_degrees(
|
163
|
+
(longitude_at_base_epoch - longitude_at_perigee).degrees % 360
|
164
|
+
)
|
165
|
+
end
|
166
|
+
|
167
|
+
def days_since_epoch
|
168
|
+
Epoch::DEFAULT_EPOCH - @epoch
|
169
|
+
end
|
170
|
+
|
171
|
+
def centuries
|
172
|
+
@centuries ||= (@epoch - Epoch::J1900) / Epoch::DAYS_PER_JULIAN_CENTURY
|
173
|
+
end
|
174
|
+
|
175
|
+
def longitude_at_base_epoch
|
176
|
+
Angle.as_degrees(
|
177
|
+
(279.6966778 + 36000.76892 * centuries + 0.0003025 * centuries**2) % 360
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
def distance_angular_size_factor
|
182
|
+
term1 = 1 + orbital_eccentricity.degrees * true_anomaly.cos
|
183
|
+
term2 = 1 - orbital_eccentricity.degrees**2
|
184
|
+
|
185
|
+
term1 / term2
|
186
|
+
end
|
187
|
+
|
188
|
+
def event_local_sidereal_time_for_date(date, observer, event)
|
189
|
+
midnight_utc = Time.utc(date.year, date.month, date.day)
|
190
|
+
epoch = Epoch.from_time(midnight_utc)
|
191
|
+
sun_at_midnight = self.class.new(epoch: epoch)
|
192
|
+
shift = Body::DEFAULT_REFRACTION_VERTICAL_SHIFT +
|
193
|
+
GeocentricParallax.angle(distance: sun_at_midnight.earth_distance) +
|
194
|
+
Angle.as_degrees(sun_at_midnight.angular_size.degrees / 2)
|
195
|
+
ecliptic_coordinates = sun_at_midnight.ecliptic_coordinates
|
196
|
+
equatorial_coordinates = ecliptic_coordinates.to_equatorial(epoch: epoch)
|
197
|
+
|
198
|
+
event_time = if event == :rising
|
199
|
+
Body.new(equatorial_coordinates).rising_time(
|
200
|
+
latitude: observer.latitude,
|
201
|
+
longitude: observer.longitude,
|
202
|
+
date: midnight_utc.to_date,
|
203
|
+
vertical_shift: shift
|
204
|
+
)
|
205
|
+
else
|
206
|
+
Body.new(equatorial_coordinates).setting_time(
|
207
|
+
latitude: observer.latitude,
|
208
|
+
longitude: observer.longitude,
|
209
|
+
date: midnight_utc.to_date,
|
210
|
+
vertical_shift: shift
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
GreenwichSiderealTime
|
215
|
+
.from_utc(event_time.utc)
|
216
|
+
.to_lst(longitude: observer.longitude)
|
217
|
+
.time
|
218
|
+
end
|
219
|
+
|
220
|
+
def vertical_shift
|
221
|
+
Astronoby::Body::DEFAULT_REFRACTION_VERTICAL_SHIFT +
|
222
|
+
Astronoby::GeocentricParallax.angle(distance: earth_distance) +
|
223
|
+
Astronoby::Angle.as_degrees(angular_size.degrees / 2)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Body
|
5
|
+
DEFAULT_REFRACTION_VERTICAL_SHIFT = Angle.as_dms(0, 34, 0)
|
6
|
+
RISING_SETTING_HOUR_ANGLE_RATIO_RANGE = (-1..1)
|
7
|
+
|
8
|
+
def initialize(equatorial_coordinates)
|
9
|
+
@equatorial_coordinates = equatorial_coordinates
|
10
|
+
end
|
11
|
+
|
12
|
+
# Source:
|
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
|
42
|
+
end
|
43
|
+
|
44
|
+
# Source:
|
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)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Source:
|
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
|
93
|
+
end
|
94
|
+
|
95
|
+
# Source:
|
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)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
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)
|
141
|
+
end
|
142
|
+
|
143
|
+
def azimuth_component(latitude)
|
144
|
+
declination.sin / latitude.cos
|
145
|
+
end
|
146
|
+
|
147
|
+
def right_ascension
|
148
|
+
@equatorial_coordinates.right_ascension
|
149
|
+
end
|
150
|
+
|
151
|
+
def declination
|
152
|
+
@equatorial_coordinates.declination
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Coordinates
|
5
|
+
class Ecliptic
|
6
|
+
attr_reader :latitude, :longitude
|
7
|
+
|
8
|
+
def initialize(latitude:, longitude:)
|
9
|
+
@latitude = latitude
|
10
|
+
@longitude = longitude
|
11
|
+
end
|
12
|
+
|
13
|
+
# Source:
|
14
|
+
# Title: Celestial Calculations
|
15
|
+
# Author: J. L. Lawrence
|
16
|
+
# Edition: MIT Press
|
17
|
+
# Chapter: 4 - Orbits and Coordinate Systems
|
18
|
+
def to_equatorial(epoch:)
|
19
|
+
mean_obliquity = MeanObliquity.for_epoch(epoch)
|
20
|
+
|
21
|
+
y = Angle.as_radians(
|
22
|
+
@longitude.sin * mean_obliquity.cos -
|
23
|
+
@latitude.tan * mean_obliquity.sin
|
24
|
+
)
|
25
|
+
x = Angle.as_radians(@longitude.cos)
|
26
|
+
r = Angle.atan(y.radians / x.radians)
|
27
|
+
right_ascension = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
|
28
|
+
|
29
|
+
declination = Angle.asin(
|
30
|
+
@latitude.sin * mean_obliquity.cos +
|
31
|
+
@latitude.cos * mean_obliquity.sin * @longitude.sin
|
32
|
+
)
|
33
|
+
|
34
|
+
Equatorial.new(
|
35
|
+
right_ascension: right_ascension,
|
36
|
+
declination: declination,
|
37
|
+
epoch: epoch
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|