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
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Coordinates
|
5
|
+
class Equatorial
|
6
|
+
attr_reader :declination, :right_ascension, :hour_angle, :epoch
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
declination:,
|
10
|
+
right_ascension:,
|
11
|
+
hour_angle: nil,
|
12
|
+
epoch: Epoch::DEFAULT_EPOCH
|
13
|
+
)
|
14
|
+
@right_ascension = right_ascension
|
15
|
+
@declination = declination
|
16
|
+
@hour_angle = hour_angle
|
17
|
+
@epoch = epoch
|
18
|
+
end
|
19
|
+
|
20
|
+
def compute_hour_angle(time:, longitude:)
|
21
|
+
lst = GreenwichSiderealTime
|
22
|
+
.from_utc(time.utc)
|
23
|
+
.to_lst(longitude: longitude)
|
24
|
+
|
25
|
+
ha = (lst.time - @right_ascension.hours)
|
26
|
+
ha += 24 if ha.negative?
|
27
|
+
|
28
|
+
Angle.as_hours(ha)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_horizontal(time:, latitude:, longitude:)
|
32
|
+
ha = @hour_angle || compute_hour_angle(time: time, longitude: longitude)
|
33
|
+
t0 = @declination.sin * latitude.sin +
|
34
|
+
@declination.cos * latitude.cos * ha.cos
|
35
|
+
altitude = Angle.asin(t0)
|
36
|
+
|
37
|
+
t1 = @declination.sin - latitude.sin * altitude.sin
|
38
|
+
t2 = t1 / (latitude.cos * altitude.cos)
|
39
|
+
azimuth = Angle.acos(t2)
|
40
|
+
|
41
|
+
if ha.sin.positive?
|
42
|
+
azimuth = Angle.as_degrees(BigDecimal("360") - azimuth.degrees)
|
43
|
+
end
|
44
|
+
|
45
|
+
Horizontal.new(
|
46
|
+
azimuth: azimuth,
|
47
|
+
altitude: altitude,
|
48
|
+
latitude: latitude,
|
49
|
+
longitude: longitude
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Source:
|
54
|
+
# Title: Celestial Calculations
|
55
|
+
# Author: J. L. Lawrence
|
56
|
+
# Edition: MIT Press
|
57
|
+
# Chapter: 4 - Orbits and Coordinate Systems
|
58
|
+
def to_ecliptic(epoch:)
|
59
|
+
mean_obliquity = MeanObliquity.for_epoch(epoch)
|
60
|
+
|
61
|
+
y = Angle.as_radians(
|
62
|
+
@right_ascension.sin * mean_obliquity.cos +
|
63
|
+
@declination.tan * mean_obliquity.sin
|
64
|
+
)
|
65
|
+
x = Angle.as_radians(@right_ascension.cos)
|
66
|
+
r = Angle.atan(y.radians / x.radians)
|
67
|
+
longitude = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
|
68
|
+
|
69
|
+
latitude = Angle.asin(
|
70
|
+
@declination.sin * mean_obliquity.cos -
|
71
|
+
@declination.cos * mean_obliquity.sin * @right_ascension.sin
|
72
|
+
)
|
73
|
+
|
74
|
+
Ecliptic.new(
|
75
|
+
latitude: latitude,
|
76
|
+
longitude: longitude
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_epoch(epoch)
|
81
|
+
Precession.for_equatorial_coordinates(
|
82
|
+
coordinates: self,
|
83
|
+
epoch: epoch
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Coordinates
|
5
|
+
class Horizontal
|
6
|
+
attr_reader :azimuth, :altitude, :latitude, :longitude
|
7
|
+
|
8
|
+
def initialize(
|
9
|
+
azimuth:,
|
10
|
+
altitude:,
|
11
|
+
latitude:,
|
12
|
+
longitude:
|
13
|
+
)
|
14
|
+
@azimuth = azimuth
|
15
|
+
@altitude = altitude
|
16
|
+
@latitude = latitude
|
17
|
+
@longitude = longitude
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_equatorial(time:)
|
21
|
+
t0 = @altitude.sin * @latitude.sin +
|
22
|
+
@altitude.cos * @latitude.cos * @azimuth.cos
|
23
|
+
|
24
|
+
declination = Angle.asin(t0)
|
25
|
+
|
26
|
+
t1 = @altitude.sin - @latitude.sin * declination.sin
|
27
|
+
|
28
|
+
hour_angle_degrees = Angle
|
29
|
+
.acos(t1 / (@latitude.cos * declination.cos))
|
30
|
+
.degrees
|
31
|
+
|
32
|
+
if @azimuth.sin.positive?
|
33
|
+
hour_angle_degrees = Angle
|
34
|
+
.as_degrees(BigDecimal("360") - hour_angle_degrees)
|
35
|
+
.degrees
|
36
|
+
end
|
37
|
+
|
38
|
+
hour_angle_hours = Angle.as_degrees(hour_angle_degrees).hours
|
39
|
+
lst = GreenwichSiderealTime
|
40
|
+
.from_utc(time.utc)
|
41
|
+
.to_lst(longitude: @longitude)
|
42
|
+
right_ascension_decimal = lst.time - hour_angle_hours
|
43
|
+
right_ascension_decimal += 24 if right_ascension_decimal.negative?
|
44
|
+
right_ascension = Angle.as_hours(right_ascension_decimal)
|
45
|
+
|
46
|
+
Equatorial.new(
|
47
|
+
right_ascension: right_ascension,
|
48
|
+
declination: declination
|
49
|
+
)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Epoch
|
5
|
+
B1900 = 2415020.3135
|
6
|
+
J1900 = 2415020.0
|
7
|
+
B1950 = 2433282.4235
|
8
|
+
J1950 = 2433282.5
|
9
|
+
J2000 = 2451545.0
|
10
|
+
|
11
|
+
DEFAULT_EPOCH = J2000
|
12
|
+
DAYS_PER_JULIAN_CENTURY = 36525.0
|
13
|
+
|
14
|
+
JULIAN_DAY_NUMBER_OFFSET = 0.5
|
15
|
+
|
16
|
+
def self.from_time(time)
|
17
|
+
time.to_datetime.ajd
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.to_utc(epoch)
|
21
|
+
DateTime.jd(epoch + JULIAN_DAY_NUMBER_OFFSET).to_time.utc
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -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
|
@@ -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,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module Astronoby
|
6
|
+
class MeanObliquity
|
7
|
+
# Source:
|
8
|
+
# IAU resolution in 2006 in favor of the P03 astronomical model
|
9
|
+
# The Astronomical Almanac for 2010
|
10
|
+
|
11
|
+
EPOCH_OF_REFERENCE = Epoch::DEFAULT_EPOCH
|
12
|
+
OBLIQUITY_OF_REFERENCE = 23.4392794
|
13
|
+
|
14
|
+
def self.for_epoch(epoch)
|
15
|
+
return obliquity_of_reference if epoch == EPOCH_OF_REFERENCE
|
16
|
+
|
17
|
+
t = (epoch - EPOCH_OF_REFERENCE) / Epoch::DAYS_PER_JULIAN_CENTURY
|
18
|
+
|
19
|
+
Angle.as_degrees(
|
20
|
+
obliquity_of_reference.degrees - (
|
21
|
+
46.815 * t -
|
22
|
+
0.0006 * t * t +
|
23
|
+
0.00181 * t * t * t
|
24
|
+
) / 3600
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.obliquity_of_reference
|
29
|
+
Angle.as_dms(23, 26, 21.45)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Nutation
|
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: 35 - Nutation
|
10
|
+
|
11
|
+
def initialize(epoch)
|
12
|
+
@epoch = epoch
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.for_ecliptic_longitude(epoch:)
|
16
|
+
new(epoch).for_ecliptic_longitude
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.for_obliquity_of_the_ecliptic(epoch:)
|
20
|
+
new(epoch).for_obliquity_of_the_ecliptic
|
21
|
+
end
|
22
|
+
|
23
|
+
def for_ecliptic_longitude
|
24
|
+
Angle.as_dms(
|
25
|
+
0,
|
26
|
+
0,
|
27
|
+
(
|
28
|
+
-17.2 * moon_ascending_node_longitude.sin -
|
29
|
+
1.3 * Math.sin(2 * sun_mean_longitude.radians)
|
30
|
+
)
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def for_obliquity_of_the_ecliptic
|
35
|
+
Angle.as_dms(
|
36
|
+
0,
|
37
|
+
0,
|
38
|
+
(
|
39
|
+
9.2 * moon_ascending_node_longitude.cos +
|
40
|
+
0.5 * Math.cos(2 * sun_mean_longitude.radians)
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def julian_centuries
|
48
|
+
(@epoch - Epoch::J1900) / Epoch::DAYS_PER_JULIAN_CENTURY
|
49
|
+
end
|
50
|
+
|
51
|
+
def sun_mean_longitude
|
52
|
+
Angle.as_degrees(
|
53
|
+
(279.6967 + 360.0 * (centuries_a - centuries_a.to_i)) % 360
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def moon_ascending_node_longitude
|
58
|
+
Angle.as_degrees(
|
59
|
+
(259.1833 - 360.0 * (centuries_b - centuries_b.to_i)) % 360
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def centuries_a
|
64
|
+
100.002136 * julian_centuries
|
65
|
+
end
|
66
|
+
|
67
|
+
def centuries_b
|
68
|
+
5.372617 * julian_centuries
|
69
|
+
end
|
70
|
+
end
|
71
|
+
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
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "matrix"
|
4
|
+
|
5
|
+
module Astronoby
|
6
|
+
class Precession
|
7
|
+
def self.for_equatorial_coordinates(coordinates:, epoch:)
|
8
|
+
new(coordinates, epoch).precess
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(coordinates, epoch)
|
12
|
+
@coordinates = coordinates
|
13
|
+
@epoch = epoch
|
14
|
+
end
|
15
|
+
|
16
|
+
# Source:
|
17
|
+
# Title: Practical Astronomy with your Calculator or Spreadsheet
|
18
|
+
# Authors: Peter Duffett-Smith and Jonathan Zwart
|
19
|
+
# Edition: Cambridge University Press
|
20
|
+
# Chapter: 34 - Precession
|
21
|
+
def precess
|
22
|
+
matrix_a = matrix_for_epoch(@coordinates.epoch)
|
23
|
+
matrix_b = matrix_for_epoch(@epoch).transpose
|
24
|
+
|
25
|
+
vector = Vector[
|
26
|
+
@coordinates.right_ascension.cos * @coordinates.declination.cos,
|
27
|
+
@coordinates.right_ascension.sin * @coordinates.declination.cos,
|
28
|
+
@coordinates.declination.sin
|
29
|
+
]
|
30
|
+
|
31
|
+
s = matrix_a * vector
|
32
|
+
w = matrix_b * s
|
33
|
+
|
34
|
+
Coordinates::Equatorial.new(
|
35
|
+
right_ascension: Util::Trigonometry.adjustement_for_arctangent(
|
36
|
+
Angle.as_radians(w[1]),
|
37
|
+
Angle.as_radians(w[0]),
|
38
|
+
Angle.atan(w[1] / w[0])
|
39
|
+
),
|
40
|
+
declination: Angle.asin(w[2]),
|
41
|
+
epoch: @epoch
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def matrix_for_epoch(epoch)
|
48
|
+
t = (epoch - Epoch::DEFAULT_EPOCH) / Epoch::DAYS_PER_JULIAN_CENTURY
|
49
|
+
|
50
|
+
zeta = Angle.as_degrees(
|
51
|
+
0.6406161 * t + 0.0000839 * t * t + 0.000005 * t * t * t
|
52
|
+
)
|
53
|
+
z = Angle.as_degrees(
|
54
|
+
0.6406161 * t + 0.0003041 * t * t + 0.0000051 * t * t * t
|
55
|
+
)
|
56
|
+
theta = Angle.as_degrees(
|
57
|
+
0.5567530 * t - 0.0001185 * t * t - 0.0000116 * t * t * t
|
58
|
+
)
|
59
|
+
|
60
|
+
cx = zeta.cos
|
61
|
+
sx = zeta.sin
|
62
|
+
cz = z.cos
|
63
|
+
sz = z.sin
|
64
|
+
ct = theta.cos
|
65
|
+
st = theta.sin
|
66
|
+
|
67
|
+
Matrix[
|
68
|
+
[
|
69
|
+
cx * ct * cz - sx * sz,
|
70
|
+
cx * ct * sz + sx * cz,
|
71
|
+
cx * st
|
72
|
+
],
|
73
|
+
[
|
74
|
+
-sx * ct * cz - cx * sz,
|
75
|
+
-sx * ct * sz + cx * cz,
|
76
|
+
-sx * st
|
77
|
+
],
|
78
|
+
[
|
79
|
+
-st * cz,
|
80
|
+
-st * sz,
|
81
|
+
ct
|
82
|
+
]
|
83
|
+
]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|