astronoby 0.9.0 → 0.11.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/.ruby-version +1 -1
- data/CHANGELOG.md +235 -0
- data/README.md +19 -1
- data/UPGRADING.md +270 -0
- data/docs/README.md +105 -16
- data/docs/altitude_crossings.md +92 -0
- data/docs/angles.md +1 -0
- data/docs/configuration.md +28 -17
- data/docs/coordinates.md +72 -12
- data/docs/deep_sky_bodies.md +46 -1
- data/docs/ephem.md +5 -2
- data/docs/equinoxes_solstices_times.md +4 -3
- data/docs/glossary.md +99 -1
- data/docs/iers.md +40 -0
- data/docs/instant.md +33 -21
- data/docs/lunar_eclipses.md +208 -0
- data/docs/lunar_observation.md +87 -0
- data/docs/moon_phases.md +4 -1
- data/docs/observer.md +20 -6
- data/docs/planetary_phenomena.md +78 -0
- data/docs/reference_frames.md +192 -34
- data/docs/rise_transit_set_times.md +6 -4
- data/docs/solar_system_bodies.md +26 -4
- data/docs/twilight_times.md +43 -24
- data/lib/astronoby/angle.rb +63 -2
- data/lib/astronoby/angles/dms.rb +18 -1
- data/lib/astronoby/angles/hms.rb +14 -1
- data/lib/astronoby/angular_velocity.rb +21 -0
- data/lib/astronoby/bodies/deep_sky_object.rb +6 -1
- data/lib/astronoby/bodies/deep_sky_object_position.rb +35 -38
- data/lib/astronoby/bodies/earth.rb +7 -44
- data/lib/astronoby/bodies/jupiter.rb +10 -0
- data/lib/astronoby/bodies/mars.rb +10 -0
- data/lib/astronoby/bodies/mercury.rb +10 -0
- data/lib/astronoby/bodies/moon.rb +158 -32
- data/lib/astronoby/bodies/neptune.rb +10 -0
- data/lib/astronoby/bodies/saturn.rb +10 -0
- data/lib/astronoby/bodies/solar_system_body.rb +240 -61
- data/lib/astronoby/bodies/sun.rb +79 -4
- data/lib/astronoby/bodies/uranus.rb +10 -0
- data/lib/astronoby/bodies/venus.rb +10 -0
- data/lib/astronoby/body.rb +6 -0
- data/lib/astronoby/cache.rb +13 -0
- data/lib/astronoby/center.rb +84 -0
- data/lib/astronoby/constants.rb +1 -2
- data/lib/astronoby/constellation.rb +9 -1
- data/lib/astronoby/constellations/data.rb +1 -1
- data/lib/astronoby/coordinates/ecliptic.rb +10 -1
- data/lib/astronoby/coordinates/equatorial.rb +79 -13
- data/lib/astronoby/coordinates/geodetic.rb +102 -0
- data/lib/astronoby/coordinates/horizontal.rb +13 -1
- data/lib/astronoby/delta_t.rb +44 -0
- data/lib/astronoby/distance.rb +35 -0
- data/lib/astronoby/duration.rb +116 -0
- data/lib/astronoby/earth_rotation.rb +70 -0
- data/lib/astronoby/equinox_solstice.rb +31 -8
- data/lib/astronoby/errors.rb +11 -0
- data/lib/astronoby/events/altitude_crossing.rb +29 -0
- data/lib/astronoby/events/altitude_crossing_calculator.rb +511 -0
- data/lib/astronoby/events/altitude_extreme.rb +31 -0
- data/lib/astronoby/events/conjunction.rb +51 -0
- data/lib/astronoby/events/conjunction_opposition_calculator.rb +84 -0
- data/lib/astronoby/events/extremum_calculator.rb +23 -176
- data/lib/astronoby/events/greatest_elongation.rb +58 -0
- data/lib/astronoby/events/greatest_elongation_calculator.rb +56 -0
- data/lib/astronoby/events/lunar_eclipse.rb +111 -0
- data/lib/astronoby/events/lunar_eclipse_calculator.rb +397 -0
- data/lib/astronoby/events/lunar_eclipse_geometry.rb +115 -0
- data/lib/astronoby/events/lunar_eclipse_phase.rb +45 -0
- data/lib/astronoby/events/lunar_eclipse_visibility.rb +296 -0
- data/lib/astronoby/events/opposition.rb +19 -0
- data/lib/astronoby/events/rise_transit_set_calculator.rb +9 -13
- data/lib/astronoby/events/rise_transit_set_event.rb +12 -1
- data/lib/astronoby/events/rise_transit_set_events.rb +12 -1
- data/lib/astronoby/events/twilight_calculator.rb +37 -147
- data/lib/astronoby/events/twilight_event.rb +24 -6
- data/lib/astronoby/events/twilight_events.rb +26 -6
- data/lib/astronoby/extremum_finder.rb +148 -0
- data/lib/astronoby/horizon.rb +40 -0
- data/lib/astronoby/horizontal_transformation.rb +112 -0
- data/lib/astronoby/instant.rb +56 -16
- data/lib/astronoby/libration.rb +25 -0
- data/lib/astronoby/mean_obliquity.rb +8 -0
- data/lib/astronoby/moon_orientation_ephemeris.rb +69 -0
- data/lib/astronoby/moon_physical_ephemeris.rb +263 -0
- data/lib/astronoby/nutation.rb +10 -20
- data/lib/astronoby/observer.rb +67 -49
- data/lib/astronoby/orientation.rb +107 -0
- data/lib/astronoby/position.rb +16 -0
- data/lib/astronoby/precession.rb +62 -61
- data/lib/astronoby/reference_frame.rb +80 -9
- data/lib/astronoby/reference_frames/apparent.rb +43 -12
- data/lib/astronoby/reference_frames/astrometric.rb +14 -1
- data/lib/astronoby/reference_frames/geometric.rb +7 -1
- data/lib/astronoby/reference_frames/mean_of_date.rb +19 -1
- data/lib/astronoby/reference_frames/teme.rb +159 -0
- data/lib/astronoby/reference_frames/topocentric.rb +36 -4
- data/lib/astronoby/refraction.rb +26 -5
- data/lib/astronoby/root_finder.rb +90 -0
- data/lib/astronoby/rotation.rb +49 -0
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +19 -8
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +44 -5
- data/lib/astronoby/time/greenwich_sidereal_time.rb +21 -0
- data/lib/astronoby/time/local_apparent_sidereal_time.rb +21 -0
- data/lib/astronoby/time/local_mean_sidereal_time.rb +21 -0
- data/lib/astronoby/time/local_sidereal_time.rb +24 -0
- data/lib/astronoby/time/sidereal_time.rb +23 -1
- data/lib/astronoby/true_obliquity.rb +4 -0
- data/lib/astronoby/util/maths.rb +8 -0
- data/lib/astronoby/util/time.rb +0 -497
- data/lib/astronoby/vector.rb +10 -0
- data/lib/astronoby/velocity.rb +39 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +30 -0
- metadata +80 -5
data/lib/astronoby/constants.rb
CHANGED
|
@@ -36,6 +36,7 @@ module Astronoby
|
|
|
36
36
|
|
|
37
37
|
# WGS84 Earth Constants
|
|
38
38
|
WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS = 6378137
|
|
39
|
+
IAU_MOON_RADIUS_IN_METERS = 0.2725076 * WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS
|
|
39
40
|
WGS84_INVERSE_FLATTENING = 298.257223563
|
|
40
41
|
WGS84_FLATTENING = 1.0 / WGS84_INVERSE_FLATTENING
|
|
41
42
|
WGS84_ECCENTICITY_SQUARED = 2.0 * WGS84_FLATTENING - WGS84_FLATTENING * WGS84_FLATTENING
|
|
@@ -45,7 +46,5 @@ module Astronoby
|
|
|
45
46
|
EARTH_FLATTENING_CORRECTION = 0.996647
|
|
46
47
|
|
|
47
48
|
EARTH_ANGULAR_VELOCITY_RAD_PER_S = 7.2921159e-5
|
|
48
|
-
|
|
49
|
-
TAI_TT_OFFSET = 32.184
|
|
50
49
|
end
|
|
51
50
|
end
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents an IAU constellation with its full name and standard
|
|
5
|
+
# three-letter abbreviation.
|
|
4
6
|
class Constellation
|
|
5
|
-
|
|
7
|
+
# @return [String] the constellation name (e.g., "Orion")
|
|
8
|
+
attr_reader :name
|
|
6
9
|
|
|
10
|
+
# @return [String] the IAU three-letter abbreviation (e.g., "Ori")
|
|
11
|
+
attr_reader :abbreviation
|
|
12
|
+
|
|
13
|
+
# @param name [String] the constellation name
|
|
14
|
+
# @param abbreviation [String] the IAU abbreviation
|
|
7
15
|
def initialize(name, abbreviation)
|
|
8
16
|
@name = name
|
|
9
17
|
@abbreviation = abbreviation
|
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
4
|
module Coordinates
|
|
5
|
+
# Ecliptic coordinate system (latitude and longitude relative to the
|
|
6
|
+
# ecliptic plane).
|
|
5
7
|
class Ecliptic
|
|
6
|
-
|
|
8
|
+
# @return [Astronoby::Angle] ecliptic latitude
|
|
9
|
+
attr_reader :latitude
|
|
7
10
|
|
|
11
|
+
# @return [Astronoby::Angle] ecliptic longitude
|
|
12
|
+
attr_reader :longitude
|
|
13
|
+
|
|
14
|
+
# @param latitude [Astronoby::Angle] ecliptic latitude
|
|
15
|
+
# @param longitude [Astronoby::Angle] ecliptic longitude
|
|
8
16
|
def initialize(latitude:, longitude:)
|
|
9
17
|
@latitude = latitude
|
|
10
18
|
@longitude = longitude
|
|
11
19
|
end
|
|
12
20
|
|
|
21
|
+
# @return [Astronoby::Coordinates::Ecliptic] zero coordinates
|
|
13
22
|
def self.zero
|
|
14
23
|
new(latitude: Angle.zero, longitude: Angle.zero)
|
|
15
24
|
end
|
|
@@ -2,9 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
4
|
module Coordinates
|
|
5
|
+
# Equatorial coordinate system (right ascension and declination).
|
|
5
6
|
class Equatorial
|
|
6
|
-
|
|
7
|
+
# @return [Astronoby::Angle] declination
|
|
8
|
+
attr_reader :declination
|
|
7
9
|
|
|
10
|
+
# @return [Astronoby::Angle] right ascension
|
|
11
|
+
attr_reader :right_ascension
|
|
12
|
+
|
|
13
|
+
# @return [Astronoby::Angle, nil] hour angle, if set
|
|
14
|
+
attr_reader :hour_angle
|
|
15
|
+
|
|
16
|
+
# @return [Numeric] the Julian Date epoch
|
|
17
|
+
attr_reader :epoch
|
|
18
|
+
|
|
19
|
+
# @param declination [Astronoby::Angle] declination
|
|
20
|
+
# @param right_ascension [Astronoby::Angle] right ascension
|
|
21
|
+
# @param hour_angle [Astronoby::Angle, nil] hour angle
|
|
22
|
+
# @param epoch [Numeric] Julian Date epoch (default: J2000.0 = 2451545.0)
|
|
8
23
|
def initialize(
|
|
9
24
|
declination:,
|
|
10
25
|
right_ascension:,
|
|
@@ -17,12 +32,23 @@ module Astronoby
|
|
|
17
32
|
@epoch = epoch
|
|
18
33
|
end
|
|
19
34
|
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
# @param epoch [Numeric] Julian Date epoch the coordinates are referred to
|
|
36
|
+
# @return [Astronoby::Coordinates::Equatorial] zero coordinates
|
|
37
|
+
def self.zero(epoch: JulianDate::DEFAULT_EPOCH)
|
|
38
|
+
new(
|
|
39
|
+
declination: Angle.zero,
|
|
40
|
+
right_ascension: Angle.zero,
|
|
41
|
+
epoch: epoch
|
|
42
|
+
)
|
|
22
43
|
end
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
# Derives equatorial coordinates from a position vector.
|
|
46
|
+
#
|
|
47
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
48
|
+
# @param epoch [Numeric] Julian Date epoch the vector is referred to
|
|
49
|
+
# @return [Astronoby::Coordinates::Equatorial] equatorial coordinates
|
|
50
|
+
def self.from_position_vector(position, epoch: JulianDate::DEFAULT_EPOCH)
|
|
51
|
+
return zero(epoch: epoch) if position.zero?
|
|
26
52
|
|
|
27
53
|
term1 = position.z.m
|
|
28
54
|
term2 = position.magnitude.m
|
|
@@ -38,9 +64,18 @@ module Astronoby
|
|
|
38
64
|
angle
|
|
39
65
|
)
|
|
40
66
|
|
|
41
|
-
new(
|
|
67
|
+
new(
|
|
68
|
+
declination: declination,
|
|
69
|
+
right_ascension: right_ascension,
|
|
70
|
+
epoch: epoch
|
|
71
|
+
)
|
|
42
72
|
end
|
|
43
73
|
|
|
74
|
+
# Computes the hour angle for a given time and observer longitude.
|
|
75
|
+
#
|
|
76
|
+
# @param time [Time] the UTC time
|
|
77
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
78
|
+
# @return [Astronoby::Angle] the hour angle
|
|
44
79
|
def compute_hour_angle(time:, longitude:)
|
|
45
80
|
last = LocalApparentSiderealTime.from_utc(time.utc, longitude: longitude)
|
|
46
81
|
ha = (last.time - @right_ascension.hours)
|
|
@@ -49,6 +84,11 @@ module Astronoby
|
|
|
49
84
|
Angle.from_hours(ha)
|
|
50
85
|
end
|
|
51
86
|
|
|
87
|
+
# Converts to horizontal coordinates for a given observer and time.
|
|
88
|
+
#
|
|
89
|
+
# @param time [Time] the UTC time
|
|
90
|
+
# @param observer [Astronoby::Observer] the observer
|
|
91
|
+
# @return [Astronoby::Coordinates::Horizontal] horizontal coordinates
|
|
52
92
|
def to_horizontal(time:, observer:)
|
|
53
93
|
latitude = observer.latitude
|
|
54
94
|
longitude = observer.longitude
|
|
@@ -74,25 +114,32 @@ module Astronoby
|
|
|
74
114
|
)
|
|
75
115
|
end
|
|
76
116
|
|
|
117
|
+
# Converts to ecliptic coordinates.
|
|
118
|
+
#
|
|
77
119
|
# Source:
|
|
78
120
|
# Title: Celestial Calculations
|
|
79
121
|
# Author: J. L. Lawrence
|
|
80
122
|
# Edition: MIT Press
|
|
81
123
|
# Chapter: 4 - Orbits and Coordinate Systems
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
#
|
|
125
|
+
# @param instant [Astronoby::Instant] the time instant for the obliquity
|
|
126
|
+
# @param obliquity [Astronoby::Angle] the obliquity of the ecliptic to
|
|
127
|
+
# rotate by. Defaults to the mean obliquity of date; pass the true
|
|
128
|
+
# obliquity when the equatorial coordinates already include nutation
|
|
129
|
+
# (true-of-date apparent and topocentric places).
|
|
130
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates
|
|
131
|
+
def to_ecliptic(instant:, obliquity: MeanObliquity.at(instant))
|
|
85
132
|
y = Angle.from_radians(
|
|
86
|
-
@right_ascension.sin *
|
|
87
|
-
@declination.tan *
|
|
133
|
+
@right_ascension.sin * obliquity.cos +
|
|
134
|
+
@declination.tan * obliquity.sin
|
|
88
135
|
)
|
|
89
136
|
x = Angle.from_radians(@right_ascension.cos)
|
|
90
137
|
r = Angle.atan(y.radians / x.radians)
|
|
91
138
|
longitude = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
|
|
92
139
|
|
|
93
140
|
latitude = Angle.asin(
|
|
94
|
-
@declination.sin *
|
|
95
|
-
@declination.cos *
|
|
141
|
+
@declination.sin * obliquity.cos -
|
|
142
|
+
@declination.cos * obliquity.sin * @right_ascension.sin
|
|
96
143
|
)
|
|
97
144
|
|
|
98
145
|
Ecliptic.new(
|
|
@@ -100,6 +147,25 @@ module Astronoby
|
|
|
100
147
|
longitude: longitude
|
|
101
148
|
)
|
|
102
149
|
end
|
|
150
|
+
|
|
151
|
+
# Position angle of another point as seen from this one, measured
|
|
152
|
+
# eastward (counterclockwise) from the direction of the north celestial
|
|
153
|
+
# pole. Both points must be expressed in the same frame.
|
|
154
|
+
#
|
|
155
|
+
# @param other [Astronoby::Coordinates::Equatorial] the target point
|
|
156
|
+
# @return [Astronoby::Angle] position angle, between -180 and 180 degrees
|
|
157
|
+
def position_angle_to(other)
|
|
158
|
+
delta_right_ascension = other.right_ascension - @right_ascension
|
|
159
|
+
|
|
160
|
+
Angle.from_radians(
|
|
161
|
+
Math.atan2(
|
|
162
|
+
other.declination.cos * delta_right_ascension.sin,
|
|
163
|
+
other.declination.sin * @declination.cos -
|
|
164
|
+
other.declination.cos * @declination.sin *
|
|
165
|
+
delta_right_ascension.cos
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
end
|
|
103
169
|
end
|
|
104
170
|
end
|
|
105
171
|
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
module Coordinates
|
|
5
|
+
# Geodetic coordinate system (WGS-84 latitude, longitude, and elevation).
|
|
6
|
+
#
|
|
7
|
+
# Geodetic coordinates describe a position on or above the Earth's surface
|
|
8
|
+
# using the WGS-84 reference ellipsoid, the same system used by GPS.
|
|
9
|
+
#
|
|
10
|
+
# The reverse conversion from ECEF (Earth-Centered Earth-Fixed) Cartesian
|
|
11
|
+
# coordinates uses Bowring's iterative method, which converges in 2-3
|
|
12
|
+
# iterations for typical satellite altitudes.
|
|
13
|
+
class Geodetic
|
|
14
|
+
SEMI_MINOR_AXIS =
|
|
15
|
+
Constants::WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS *
|
|
16
|
+
(1 - Constants::WGS84_FLATTENING)
|
|
17
|
+
SECOND_ECCENTRICITY_SQUARED =
|
|
18
|
+
(Constants::WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS**2 -
|
|
19
|
+
SEMI_MINOR_AXIS**2) /
|
|
20
|
+
SEMI_MINOR_AXIS**2
|
|
21
|
+
MAX_ITERATIONS = 10
|
|
22
|
+
CONVERGENCE_THRESHOLD = 1e-12
|
|
23
|
+
|
|
24
|
+
# @return [Astronoby::Angle] geodetic latitude
|
|
25
|
+
attr_reader :latitude
|
|
26
|
+
|
|
27
|
+
# @return [Astronoby::Angle] geodetic longitude
|
|
28
|
+
attr_reader :longitude
|
|
29
|
+
|
|
30
|
+
# @return [Astronoby::Distance] elevation above the WGS-84 ellipsoid
|
|
31
|
+
attr_reader :elevation
|
|
32
|
+
|
|
33
|
+
# @param latitude [Astronoby::Angle] geodetic latitude
|
|
34
|
+
# @param longitude [Astronoby::Angle] geodetic longitude
|
|
35
|
+
# @param elevation [Astronoby::Distance] elevation above the WGS-84
|
|
36
|
+
# ellipsoid
|
|
37
|
+
def initialize(latitude:, longitude:, elevation:)
|
|
38
|
+
@latitude = latitude
|
|
39
|
+
@longitude = longitude
|
|
40
|
+
@elevation = elevation
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Converts ECEF Cartesian coordinates to geodetic coordinates using
|
|
44
|
+
# Bowring's iterative method.
|
|
45
|
+
#
|
|
46
|
+
# Source:
|
|
47
|
+
# Title: Transformation from Spatial to Geographical Coordinates
|
|
48
|
+
# Author: B. R. Bowring
|
|
49
|
+
# Edition: Survey Review, Vol. 23, No. 181, 1976
|
|
50
|
+
#
|
|
51
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] ECEF position
|
|
52
|
+
# @return [Astronoby::Coordinates::Geodetic] geodetic coordinates
|
|
53
|
+
def self.from_ecef(position)
|
|
54
|
+
x = position.x.m
|
|
55
|
+
y = position.y.m
|
|
56
|
+
z = position.z.m
|
|
57
|
+
|
|
58
|
+
a = Constants::WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS
|
|
59
|
+
e2 = Constants::WGS84_ECCENTICITY_SQUARED
|
|
60
|
+
|
|
61
|
+
p = Math.sqrt(x * x + y * y)
|
|
62
|
+
longitude = Math.atan2(y, x)
|
|
63
|
+
|
|
64
|
+
theta = Math.atan2(z * a, p * SEMI_MINOR_AXIS)
|
|
65
|
+
latitude = Math.atan2(
|
|
66
|
+
z + SECOND_ECCENTRICITY_SQUARED * SEMI_MINOR_AXIS *
|
|
67
|
+
Math.sin(theta)**3,
|
|
68
|
+
p - e2 * a * Math.cos(theta)**3
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
MAX_ITERATIONS.times do
|
|
72
|
+
prev_latitude = latitude
|
|
73
|
+
theta = Math.atan2(
|
|
74
|
+
(1 - Constants::WGS84_FLATTENING) * Math.sin(latitude),
|
|
75
|
+
Math.cos(latitude)
|
|
76
|
+
)
|
|
77
|
+
latitude = Math.atan2(
|
|
78
|
+
z + SECOND_ECCENTRICITY_SQUARED * SEMI_MINOR_AXIS *
|
|
79
|
+
Math.sin(theta)**3,
|
|
80
|
+
p - e2 * a * Math.cos(theta)**3
|
|
81
|
+
)
|
|
82
|
+
break if (latitude - prev_latitude).abs < CONVERGENCE_THRESHOLD
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
sin_lat = Math.sin(latitude)
|
|
86
|
+
n = a / Math.sqrt(1 - e2 * sin_lat * sin_lat)
|
|
87
|
+
|
|
88
|
+
elevation = if Math.cos(latitude).abs > 1e-10
|
|
89
|
+
p / Math.cos(latitude) - n
|
|
90
|
+
else
|
|
91
|
+
z / sin_lat - n * (1 - e2)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
new(
|
|
95
|
+
latitude: Angle.from_radians(latitude),
|
|
96
|
+
longitude: Angle.from_radians(longitude),
|
|
97
|
+
elevation: Distance.from_meters(elevation)
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -2,9 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
4
|
module Coordinates
|
|
5
|
+
# Horizontal coordinate system (azimuth and altitude) for a specific
|
|
6
|
+
# observer.
|
|
5
7
|
class Horizontal
|
|
6
|
-
|
|
8
|
+
# @return [Astronoby::Angle] azimuth (measured from north, clockwise)
|
|
9
|
+
attr_reader :azimuth
|
|
7
10
|
|
|
11
|
+
# @return [Astronoby::Angle] altitude above the horizon
|
|
12
|
+
attr_reader :altitude
|
|
13
|
+
|
|
14
|
+
# @return [Astronoby::Observer] the observer
|
|
15
|
+
attr_reader :observer
|
|
16
|
+
|
|
17
|
+
# @param azimuth [Astronoby::Angle] azimuth
|
|
18
|
+
# @param altitude [Astronoby::Angle] altitude
|
|
19
|
+
# @param observer [Astronoby::Observer] the observer
|
|
8
20
|
def initialize(
|
|
9
21
|
azimuth:,
|
|
10
22
|
altitude:,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "iers"
|
|
4
|
+
|
|
5
|
+
module Astronoby
|
|
6
|
+
module DeltaT
|
|
7
|
+
class << self
|
|
8
|
+
# @param instant [Numeric, Time, Date, DateTime] a Julian Date, or a date
|
|
9
|
+
# and time. Any time scale will do.
|
|
10
|
+
# @return [Numeric] ΔT in seconds
|
|
11
|
+
# @raise [Astronoby::IncompatibleArgumentsError] if the instant is not a
|
|
12
|
+
# Numeric, Time, Date or DateTime
|
|
13
|
+
def at(instant)
|
|
14
|
+
case instant
|
|
15
|
+
when Numeric
|
|
16
|
+
IERS::DeltaT.at(jd: instant).delta_t
|
|
17
|
+
when ::Time, ::Date, ::DateTime
|
|
18
|
+
IERS::DeltaT.at(instant).delta_t
|
|
19
|
+
else
|
|
20
|
+
raise IncompatibleArgumentsError,
|
|
21
|
+
"Expected a Numeric, Time, Date or DateTime object, got #{instant.class}"
|
|
22
|
+
end
|
|
23
|
+
rescue IERS::OutOfRangeError => error
|
|
24
|
+
outside_available_range(error)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# IERS covers 1800 up to the end of its EOP series, and refuses anything
|
|
30
|
+
# outside. Rather than propagate that, we answer with the closest value
|
|
31
|
+
# we have: 0 before 1800, where ΔT is small next to the uncertainty on
|
|
32
|
+
# it, and the last measured value after the series, which drifts slowly
|
|
33
|
+
# enough to beat any extrapolation we could invent.
|
|
34
|
+
def outside_available_range(error)
|
|
35
|
+
last_measured = IERS::Data.finals_entries.last
|
|
36
|
+
return 0 if last_measured.nil?
|
|
37
|
+
return 0 if error.requested_mjd.nil?
|
|
38
|
+
return 0 if error.requested_mjd < last_measured.mjd
|
|
39
|
+
|
|
40
|
+
IERS::DeltaT.at(mjd: last_measured.mjd).delta_t
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/astronoby/distance.rb
CHANGED
|
@@ -1,93 +1,128 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents a distance with meters as its internal representation.
|
|
5
|
+
# Provides conversions between meters, kilometers, astronomical units,
|
|
6
|
+
# and parsecs.
|
|
7
|
+
#
|
|
8
|
+
# @example Create a distance from astronomical units
|
|
9
|
+
# distance = Astronoby::Distance.from_au(1.0)
|
|
10
|
+
# distance.km # => 149597870.7
|
|
11
|
+
#
|
|
4
12
|
class Distance
|
|
5
13
|
include Comparable
|
|
6
14
|
|
|
7
15
|
class << self
|
|
16
|
+
# @return [Astronoby::Distance] a zero distance
|
|
8
17
|
def zero
|
|
9
18
|
new(0)
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @param meters [Numeric] the distance in meters
|
|
22
|
+
# @return [Astronoby::Distance] a new Distance
|
|
12
23
|
def from_meters(meters)
|
|
13
24
|
new(meters)
|
|
14
25
|
end
|
|
15
26
|
alias_method :from_m, :from_meters
|
|
16
27
|
|
|
28
|
+
# @param kilometers [Numeric] the distance in kilometers
|
|
29
|
+
# @return [Astronoby::Distance] a new Distance
|
|
17
30
|
def from_kilometers(kilometers)
|
|
18
31
|
meters = kilometers * Constants::KILOMETER_IN_METERS
|
|
19
32
|
from_meters(meters)
|
|
20
33
|
end
|
|
21
34
|
alias_method :from_km, :from_kilometers
|
|
22
35
|
|
|
36
|
+
# @param astronomical_units [Numeric] the distance in AU
|
|
37
|
+
# @return [Astronoby::Distance] a new Distance
|
|
23
38
|
def from_astronomical_units(astronomical_units)
|
|
24
39
|
meters = astronomical_units * Constants::ASTRONOMICAL_UNIT_IN_METERS
|
|
25
40
|
from_meters(meters)
|
|
26
41
|
end
|
|
27
42
|
alias_method :from_au, :from_astronomical_units
|
|
28
43
|
|
|
44
|
+
# @param parsecs [Numeric] the distance in parsecs
|
|
45
|
+
# @return [Astronoby::Distance] a new Distance
|
|
29
46
|
def from_parsecs(parsecs)
|
|
30
47
|
meters = parsecs * Constants::PARSEC_IN_METERS
|
|
31
48
|
from_meters(meters)
|
|
32
49
|
end
|
|
33
50
|
alias_method :from_pc, :from_parsecs
|
|
34
51
|
|
|
52
|
+
# @param array [Array<Numeric>] array of meter values
|
|
53
|
+
# @return [Astronoby::Vector<Astronoby::Distance>] a vector of Distances
|
|
35
54
|
def vector_from_meters(array)
|
|
36
55
|
Vector.elements(array.map { from_meters(_1) })
|
|
37
56
|
end
|
|
38
57
|
alias_method :vector_from_m, :vector_from_meters
|
|
39
58
|
end
|
|
40
59
|
|
|
60
|
+
# @return [Numeric] the distance in meters
|
|
41
61
|
attr_reader :meters
|
|
42
62
|
alias_method :m, :meters
|
|
43
63
|
|
|
64
|
+
# @param meters [Numeric] the distance in meters
|
|
44
65
|
def initialize(meters)
|
|
45
66
|
@meters = meters
|
|
46
67
|
freeze
|
|
47
68
|
end
|
|
48
69
|
|
|
70
|
+
# @return [Float] the distance in kilometers
|
|
49
71
|
def kilometers
|
|
50
72
|
@meters / Constants::KILOMETER_IN_METERS.to_f
|
|
51
73
|
end
|
|
52
74
|
alias_method :km, :kilometers
|
|
53
75
|
|
|
76
|
+
# @return [Float] the distance in astronomical units
|
|
54
77
|
def astronomical_units
|
|
55
78
|
@meters / Constants::ASTRONOMICAL_UNIT_IN_METERS.to_f
|
|
56
79
|
end
|
|
57
80
|
alias_method :au, :astronomical_units
|
|
58
81
|
|
|
82
|
+
# @param other [Astronoby::Distance] distance to add
|
|
83
|
+
# @return [Astronoby::Distance] the sum
|
|
59
84
|
def +(other)
|
|
60
85
|
self.class.from_meters(meters + other.meters)
|
|
61
86
|
end
|
|
62
87
|
|
|
88
|
+
# @param other [Astronoby::Distance] distance to subtract
|
|
89
|
+
# @return [Astronoby::Distance] the difference
|
|
63
90
|
def -(other)
|
|
64
91
|
self.class.from_meters(@meters - other.meters)
|
|
65
92
|
end
|
|
66
93
|
|
|
94
|
+
# @return [Astronoby::Distance] the negated distance
|
|
67
95
|
def -@
|
|
68
96
|
self.class.from_meters(-@meters)
|
|
69
97
|
end
|
|
70
98
|
|
|
99
|
+
# @return [Boolean] true if the distance is positive
|
|
71
100
|
def positive?
|
|
72
101
|
meters > 0
|
|
73
102
|
end
|
|
74
103
|
|
|
104
|
+
# @return [Boolean] true if the distance is negative
|
|
75
105
|
def negative?
|
|
76
106
|
meters < 0
|
|
77
107
|
end
|
|
78
108
|
|
|
109
|
+
# @return [Boolean] true if the distance is zero
|
|
79
110
|
def zero?
|
|
80
111
|
meters.zero?
|
|
81
112
|
end
|
|
82
113
|
|
|
114
|
+
# @return [Numeric] the square of the distance in meters
|
|
83
115
|
def abs2
|
|
84
116
|
meters**2
|
|
85
117
|
end
|
|
86
118
|
|
|
119
|
+
# @return [Integer] hash value
|
|
87
120
|
def hash
|
|
88
121
|
[meters, self.class].hash
|
|
89
122
|
end
|
|
90
123
|
|
|
124
|
+
# @param other [Astronoby::Distance] distance to compare with
|
|
125
|
+
# @return [Integer, nil] -1, 0, or 1; nil if not comparable
|
|
91
126
|
def <=>(other)
|
|
92
127
|
return unless other.is_a?(self.class)
|
|
93
128
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class Duration
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
# @return [Astronoby::Duration] a zero duration
|
|
9
|
+
def zero
|
|
10
|
+
new(0)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param seconds [Numeric] the duration in seconds
|
|
14
|
+
# @return [Astronoby::Duration] a new Duration
|
|
15
|
+
def from_seconds(seconds)
|
|
16
|
+
new(seconds)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param minutes [Numeric] the duration in minutes
|
|
20
|
+
# @return [Astronoby::Duration] a new Duration
|
|
21
|
+
def from_minutes(minutes)
|
|
22
|
+
seconds = minutes * Constants::SECONDS_PER_MINUTE
|
|
23
|
+
from_seconds(seconds)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param hours [Numeric] the duration in hours
|
|
27
|
+
# @return [Astronoby::Duration] a new Duration
|
|
28
|
+
def from_hours(hours)
|
|
29
|
+
seconds = hours * Constants::SECONDS_PER_HOUR
|
|
30
|
+
from_seconds(seconds)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param days [Numeric] the duration in days
|
|
34
|
+
# @return [Astronoby::Duration] a new Duration
|
|
35
|
+
def from_days(days)
|
|
36
|
+
seconds = days * Constants::SECONDS_PER_DAY
|
|
37
|
+
from_seconds(seconds)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Numeric] the duration in seconds
|
|
42
|
+
attr_reader :seconds
|
|
43
|
+
|
|
44
|
+
# @param seconds [Numeric] the duration in seconds
|
|
45
|
+
def initialize(seconds)
|
|
46
|
+
@seconds = seconds
|
|
47
|
+
freeze
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Float] the duration in minutes
|
|
51
|
+
def minutes
|
|
52
|
+
@seconds / Constants::SECONDS_PER_MINUTE
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [Float] the duration in hours
|
|
56
|
+
def hours
|
|
57
|
+
@seconds / Constants::SECONDS_PER_HOUR
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Float] the duration in days
|
|
61
|
+
def days
|
|
62
|
+
@seconds / Constants::SECONDS_PER_DAY
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param other [Astronoby::Duration] duration to add
|
|
66
|
+
# @return [Astronoby::Duration] the sum
|
|
67
|
+
def +(other)
|
|
68
|
+
self.class.from_seconds(@seconds + other.seconds)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param other [Astronoby::Duration] duration to subtract
|
|
72
|
+
# @return [Astronoby::Duration] the difference
|
|
73
|
+
def -(other)
|
|
74
|
+
self.class.from_seconds(@seconds - other.seconds)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @return [Astronoby::Duration] the negated duration
|
|
78
|
+
def -@
|
|
79
|
+
self.class.from_seconds(-@seconds)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Astronoby::Duration] the absolute duration
|
|
83
|
+
def abs
|
|
84
|
+
self.class.from_seconds(@seconds.abs)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Boolean] true if the duration is positive
|
|
88
|
+
def positive?
|
|
89
|
+
@seconds > 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean] true if the duration is negative
|
|
93
|
+
def negative?
|
|
94
|
+
@seconds < 0
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [Boolean] true if the duration is zero
|
|
98
|
+
def zero?
|
|
99
|
+
@seconds.zero?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Integer] hash value
|
|
103
|
+
def hash
|
|
104
|
+
[@seconds, self.class].hash
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @param other [Astronoby::Duration] duration to compare with
|
|
108
|
+
# @return [Integer, nil] -1, 0, or 1; nil if not comparable
|
|
109
|
+
def <=>(other)
|
|
110
|
+
return unless other.is_a?(self.class)
|
|
111
|
+
|
|
112
|
+
seconds <=> other.seconds
|
|
113
|
+
end
|
|
114
|
+
alias_method :eql?, :==
|
|
115
|
+
end
|
|
116
|
+
end
|