astronoby 0.9.0 → 0.10.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 +101 -0
- data/README.md +6 -1
- data/UPGRADING.md +84 -0
- data/docs/README.md +80 -15
- data/docs/angles.md +1 -0
- data/docs/configuration.md +20 -17
- data/docs/coordinates.md +72 -12
- data/docs/deep_sky_bodies.md +1 -1
- data/docs/ephem.md +5 -2
- data/docs/equinoxes_solstices_times.md +4 -3
- data/docs/glossary.md +97 -1
- data/docs/iers.md +40 -0
- data/docs/instant.md +20 -15
- data/docs/lunar_eclipses.md +93 -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 +25 -21
- 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 +32 -17
- 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/center.rb +84 -0
- data/lib/astronoby/constellation.rb +9 -1
- data/lib/astronoby/coordinates/ecliptic.rb +10 -1
- data/lib/astronoby/coordinates/equatorial.rb +64 -8
- data/lib/astronoby/coordinates/geodetic.rb +102 -0
- data/lib/astronoby/coordinates/horizontal.rb +13 -1
- 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/conjunction.rb +51 -0
- data/lib/astronoby/events/conjunction_opposition_calculator.rb +84 -0
- data/lib/astronoby/events/eclipse_phase.rb +27 -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 +99 -0
- data/lib/astronoby/events/lunar_eclipse_calculator.rb +285 -0
- data/lib/astronoby/events/opposition.rb +19 -0
- 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_event.rb +24 -6
- data/lib/astronoby/events/twilight_events.rb +26 -6
- data/lib/astronoby/extremum_finder.rb +148 -0
- data/lib/astronoby/instant.rb +10 -7
- 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 +61 -60
- data/lib/astronoby/reference_frame.rb +73 -7
- data/lib/astronoby/reference_frames/apparent.rb +26 -7
- 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 +13 -1
- data/lib/astronoby/reference_frames/teme.rb +153 -0
- data/lib/astronoby/reference_frames/topocentric.rb +30 -4
- data/lib/astronoby/refraction.rb +26 -5
- data/lib/astronoby/root_finder.rb +83 -0
- data/lib/astronoby/rotation.rb +49 -0
- data/lib/astronoby/time/greenwich_apparent_sidereal_time.rb +9 -0
- data/lib/astronoby/time/greenwich_mean_sidereal_time.rb +42 -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 +10 -485
- 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 +22 -0
- metadata +45 -5
data/lib/astronoby/bodies/sun.rb
CHANGED
|
@@ -1,18 +1,77 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents the Sun. Provides twilight events, equinox/solstice
|
|
5
|
+
# calculations, and equation of time.
|
|
4
6
|
class Sun < SolarSystemBody
|
|
5
7
|
EQUATORIAL_RADIUS = Distance.from_meters(695_700_000)
|
|
6
8
|
ABSOLUTE_MAGNITUDE = -26.74
|
|
7
9
|
|
|
10
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
11
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
12
|
def self.ephemeris_segments(_ephem_source)
|
|
9
13
|
[[SOLAR_SYSTEM_BARYCENTER, SUN]]
|
|
10
14
|
end
|
|
11
15
|
|
|
16
|
+
# @return [Float] absolute magnitude
|
|
12
17
|
def self.absolute_magnitude
|
|
13
18
|
ABSOLUTE_MAGNITUDE
|
|
14
19
|
end
|
|
15
20
|
|
|
21
|
+
# @param observer [Astronoby::Observer] Observer for whom to calculate
|
|
22
|
+
# twilight events
|
|
23
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
24
|
+
# @param date [Date] Date for which to calculate twilight events (optional)
|
|
25
|
+
# @param start_time [Time] Start time for twilight event calculation
|
|
26
|
+
# (optional)
|
|
27
|
+
# @param end_time [Time] End time for twilight event calculation (optional)
|
|
28
|
+
# @param utc_offset [String] UTC offset for the given date (e.g., "+02:00")
|
|
29
|
+
# @return [Astronoby::TwilightEvent, Array<Astronoby::TwilightEvent>]
|
|
30
|
+
# Twilight events for the given date or time range.
|
|
31
|
+
def self.twilight_events(
|
|
32
|
+
observer:,
|
|
33
|
+
ephem:,
|
|
34
|
+
date: nil,
|
|
35
|
+
start_time: nil,
|
|
36
|
+
end_time: nil,
|
|
37
|
+
utc_offset: 0
|
|
38
|
+
)
|
|
39
|
+
calculator = TwilightCalculator.new(observer: observer, ephem: ephem)
|
|
40
|
+
if date
|
|
41
|
+
calculator.event_on(date, utc_offset: utc_offset)
|
|
42
|
+
else
|
|
43
|
+
calculator.events_between(start_time, end_time)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
48
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
49
|
+
# @return [Time] Time of the March equinox for the given year.
|
|
50
|
+
def self.march_equinox(year, ephem:)
|
|
51
|
+
EquinoxSolstice.march_equinox(year, ephem)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
55
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
56
|
+
# @return [Time] Time of the June solstice for the given year.
|
|
57
|
+
def self.june_solstice(year, ephem:)
|
|
58
|
+
EquinoxSolstice.june_solstice(year, ephem)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
62
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
63
|
+
# @return [Time] Time of the September equinox for the given year.
|
|
64
|
+
def self.september_equinox(year, ephem:)
|
|
65
|
+
EquinoxSolstice.september_equinox(year, ephem)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @param year [Integer] Year for which to calculate equinoxes and solstices
|
|
69
|
+
# @param ephem [::Ephem::SPK] Ephemeris data source
|
|
70
|
+
# @return [Time] Time of the December solstice for the given year.
|
|
71
|
+
def self.december_solstice(year, ephem:)
|
|
72
|
+
EquinoxSolstice.december_solstice(year, ephem)
|
|
73
|
+
end
|
|
74
|
+
|
|
16
75
|
# Source:
|
|
17
76
|
# Title: Explanatory Supplement to the Astronomical Almanac
|
|
18
77
|
# Authors: Sean E. Urban and P. Kenneth Seidelmann
|
|
@@ -31,7 +90,7 @@ module Astronoby
|
|
|
31
90
|
# Edition: 2nd edition
|
|
32
91
|
# Chapter: 28 - Equation of Time
|
|
33
92
|
|
|
34
|
-
# @return [
|
|
93
|
+
# @return [Astronoby::Duration] Equation of time
|
|
35
94
|
def equation_of_time
|
|
36
95
|
right_ascension = apparent.equatorial.right_ascension
|
|
37
96
|
t = (@instant.julian_date - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_MILLENIA
|
|
@@ -44,7 +103,7 @@ module Astronoby
|
|
|
44
103
|
nutation = Nutation.new(instant: instant).nutation_in_longitude
|
|
45
104
|
obliquity = TrueObliquity.at(@instant)
|
|
46
105
|
|
|
47
|
-
(
|
|
106
|
+
seconds = (
|
|
48
107
|
Angle
|
|
49
108
|
.from_degrees(
|
|
50
109
|
l0 -
|
|
@@ -53,12 +112,28 @@ module Astronoby
|
|
|
53
112
|
nutation.degrees * obliquity.cos
|
|
54
113
|
).hours * Constants::SECONDS_PER_HOUR
|
|
55
114
|
).round
|
|
115
|
+
Duration.from_seconds(seconds)
|
|
56
116
|
end
|
|
57
117
|
|
|
58
|
-
|
|
118
|
+
# @return [nil] the Sun has no phase angle as seen from Earth
|
|
119
|
+
def phase_angle
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @return [Boolean] always false; the Sun has no primary body
|
|
124
|
+
def approaching_primary?
|
|
125
|
+
false
|
|
126
|
+
end
|
|
59
127
|
|
|
60
|
-
|
|
128
|
+
# @return [Boolean] always false; the Sun has no primary body
|
|
129
|
+
def receding_from_primary?
|
|
61
130
|
false
|
|
62
131
|
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def primary_body_geometric
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
63
138
|
end
|
|
64
139
|
end
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents Uranus.
|
|
4
5
|
class Uranus < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(25_559_000)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -7.11
|
|
8
|
+
ORBITAL_PERIOD = 30688.5
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Uranus is a superior planet
|
|
11
|
+
def self.superior_planet?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
16
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
17
|
def self.ephemeris_segments(_ephem_source)
|
|
9
18
|
[[SOLAR_SYSTEM_BARYCENTER, URANUS_BARYCENTER]]
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @return [Float] absolute magnitude
|
|
12
22
|
def self.absolute_magnitude
|
|
13
23
|
ABSOLUTE_MAGNITUDE
|
|
14
24
|
end
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Astronoby
|
|
4
|
+
# Represents Venus.
|
|
4
5
|
class Venus < SolarSystemBody
|
|
5
6
|
EQUATORIAL_RADIUS = Distance.from_meters(6_051_800)
|
|
6
7
|
ABSOLUTE_MAGNITUDE = -4.384
|
|
8
|
+
ORBITAL_PERIOD = 224.701
|
|
7
9
|
|
|
10
|
+
# @return [Boolean] true; Venus is an inferior planet
|
|
11
|
+
def self.inferior_planet?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param _ephem_source [Symbol] the ephemeris source type
|
|
16
|
+
# @return [Array<Array>] ephemeris segment identifiers
|
|
8
17
|
def self.ephemeris_segments(_ephem_source)
|
|
9
18
|
[[SOLAR_SYSTEM_BARYCENTER, VENUS_BARYCENTER]]
|
|
10
19
|
end
|
|
11
20
|
|
|
21
|
+
# @return [Float] absolute magnitude
|
|
12
22
|
def self.absolute_magnitude
|
|
13
23
|
ABSOLUTE_MAGNITUDE
|
|
14
24
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Astronoby
|
|
4
|
+
class Center
|
|
5
|
+
BARYCENTRIC = :barycentric
|
|
6
|
+
GEOCENTRIC = :geocentric
|
|
7
|
+
TOPOCENTRIC = :topocentric
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# @return [Astronoby::Center] the Solar System barycenter
|
|
11
|
+
def barycentric
|
|
12
|
+
@barycentric ||= new(kind: BARYCENTRIC)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [Astronoby::Center] the Earth's center
|
|
16
|
+
def geocentric
|
|
17
|
+
@geocentric ||= new(kind: GEOCENTRIC)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param observer [Astronoby::Observer] the observer
|
|
21
|
+
# @return [Astronoby::Center] a center at the observer's location
|
|
22
|
+
def topocentric(observer)
|
|
23
|
+
new(kind: TOPOCENTRIC, observer: observer)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Symbol] the kind of center
|
|
28
|
+
attr_reader :kind
|
|
29
|
+
|
|
30
|
+
# @return [Astronoby::Observer, nil] the observer, for topocentric centers
|
|
31
|
+
attr_reader :observer
|
|
32
|
+
|
|
33
|
+
# @param kind [Symbol] one of BARYCENTRIC, GEOCENTRIC, TOPOCENTRIC
|
|
34
|
+
# @param observer [Astronoby::Observer, nil] the observer, for topocentric
|
|
35
|
+
def initialize(kind:, observer: nil)
|
|
36
|
+
@kind = kind
|
|
37
|
+
@observer = observer
|
|
38
|
+
freeze
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Boolean] true if the center is the Solar System barycenter
|
|
42
|
+
def barycentric?
|
|
43
|
+
@kind == BARYCENTRIC
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Boolean] true if the center is the Earth's center
|
|
47
|
+
def geocentric?
|
|
48
|
+
@kind == GEOCENTRIC
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] true if the center is at an observer's location
|
|
52
|
+
def topocentric?
|
|
53
|
+
@kind == TOPOCENTRIC
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Boolean] true if the center depends on a specific observer
|
|
57
|
+
def observer_dependent?
|
|
58
|
+
topocentric?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param other [Astronoby::Center] center to compare with
|
|
62
|
+
# @return [Boolean] true if both centers are equivalent
|
|
63
|
+
def ==(other)
|
|
64
|
+
other.is_a?(self.class) &&
|
|
65
|
+
kind == other.kind &&
|
|
66
|
+
location_key == other.location_key
|
|
67
|
+
end
|
|
68
|
+
alias_method :eql?, :==
|
|
69
|
+
|
|
70
|
+
# @return [Integer] hash value
|
|
71
|
+
def hash
|
|
72
|
+
[self.class, @kind, location_key].hash
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
protected
|
|
76
|
+
|
|
77
|
+
# @return [Array, nil] the geometric location key, or nil if not topocentric
|
|
78
|
+
def location_key
|
|
79
|
+
return unless @observer
|
|
80
|
+
|
|
81
|
+
[@observer.latitude, @observer.longitude, @observer.elevation]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
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,10 +32,15 @@ module Astronoby
|
|
|
17
32
|
@epoch = epoch
|
|
18
33
|
end
|
|
19
34
|
|
|
35
|
+
# @return [Astronoby::Coordinates::Equatorial] zero coordinates
|
|
20
36
|
def self.zero
|
|
21
37
|
new(declination: Angle.zero, right_ascension: Angle.zero)
|
|
22
38
|
end
|
|
23
39
|
|
|
40
|
+
# Derives equatorial coordinates from a position vector.
|
|
41
|
+
#
|
|
42
|
+
# @param position [Astronoby::Vector<Astronoby::Distance>] position vector
|
|
43
|
+
# @return [Astronoby::Coordinates::Equatorial] equatorial coordinates
|
|
24
44
|
def self.from_position_vector(position)
|
|
25
45
|
return zero if position.zero?
|
|
26
46
|
|
|
@@ -41,6 +61,11 @@ module Astronoby
|
|
|
41
61
|
new(declination: declination, right_ascension: right_ascension)
|
|
42
62
|
end
|
|
43
63
|
|
|
64
|
+
# Computes the hour angle for a given time and observer longitude.
|
|
65
|
+
#
|
|
66
|
+
# @param time [Time] the UTC time
|
|
67
|
+
# @param longitude [Astronoby::Angle] the observer's longitude
|
|
68
|
+
# @return [Astronoby::Angle] the hour angle
|
|
44
69
|
def compute_hour_angle(time:, longitude:)
|
|
45
70
|
last = LocalApparentSiderealTime.from_utc(time.utc, longitude: longitude)
|
|
46
71
|
ha = (last.time - @right_ascension.hours)
|
|
@@ -49,6 +74,11 @@ module Astronoby
|
|
|
49
74
|
Angle.from_hours(ha)
|
|
50
75
|
end
|
|
51
76
|
|
|
77
|
+
# Converts to horizontal coordinates for a given observer and time.
|
|
78
|
+
#
|
|
79
|
+
# @param time [Time] the UTC time
|
|
80
|
+
# @param observer [Astronoby::Observer] the observer
|
|
81
|
+
# @return [Astronoby::Coordinates::Horizontal] horizontal coordinates
|
|
52
82
|
def to_horizontal(time:, observer:)
|
|
53
83
|
latitude = observer.latitude
|
|
54
84
|
longitude = observer.longitude
|
|
@@ -74,25 +104,32 @@ module Astronoby
|
|
|
74
104
|
)
|
|
75
105
|
end
|
|
76
106
|
|
|
107
|
+
# Converts to ecliptic coordinates.
|
|
108
|
+
#
|
|
77
109
|
# Source:
|
|
78
110
|
# Title: Celestial Calculations
|
|
79
111
|
# Author: J. L. Lawrence
|
|
80
112
|
# Edition: MIT Press
|
|
81
113
|
# Chapter: 4 - Orbits and Coordinate Systems
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
#
|
|
115
|
+
# @param instant [Astronoby::Instant] the time instant for the obliquity
|
|
116
|
+
# @param obliquity [Astronoby::Angle] the obliquity of the ecliptic to
|
|
117
|
+
# rotate by. Defaults to the mean obliquity of date; pass the true
|
|
118
|
+
# obliquity when the equatorial coordinates already include nutation
|
|
119
|
+
# (true-of-date apparent and topocentric places).
|
|
120
|
+
# @return [Astronoby::Coordinates::Ecliptic] ecliptic coordinates
|
|
121
|
+
def to_ecliptic(instant:, obliquity: MeanObliquity.at(instant))
|
|
85
122
|
y = Angle.from_radians(
|
|
86
|
-
@right_ascension.sin *
|
|
87
|
-
@declination.tan *
|
|
123
|
+
@right_ascension.sin * obliquity.cos +
|
|
124
|
+
@declination.tan * obliquity.sin
|
|
88
125
|
)
|
|
89
126
|
x = Angle.from_radians(@right_ascension.cos)
|
|
90
127
|
r = Angle.atan(y.radians / x.radians)
|
|
91
128
|
longitude = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
|
|
92
129
|
|
|
93
130
|
latitude = Angle.asin(
|
|
94
|
-
@declination.sin *
|
|
95
|
-
@declination.cos *
|
|
131
|
+
@declination.sin * obliquity.cos -
|
|
132
|
+
@declination.cos * obliquity.sin * @right_ascension.sin
|
|
96
133
|
)
|
|
97
134
|
|
|
98
135
|
Ecliptic.new(
|
|
@@ -100,6 +137,25 @@ module Astronoby
|
|
|
100
137
|
longitude: longitude
|
|
101
138
|
)
|
|
102
139
|
end
|
|
140
|
+
|
|
141
|
+
# Position angle of another point as seen from this one, measured
|
|
142
|
+
# eastward (counterclockwise) from the direction of the north celestial
|
|
143
|
+
# pole. Both points must be expressed in the same frame.
|
|
144
|
+
#
|
|
145
|
+
# @param other [Astronoby::Coordinates::Equatorial] the target point
|
|
146
|
+
# @return [Astronoby::Angle] position angle, between -180 and 180 degrees
|
|
147
|
+
def position_angle_to(other)
|
|
148
|
+
delta_right_ascension = other.right_ascension - @right_ascension
|
|
149
|
+
|
|
150
|
+
Angle.from_radians(
|
|
151
|
+
Math.atan2(
|
|
152
|
+
other.declination.cos * delta_right_ascension.sin,
|
|
153
|
+
other.declination.sin * @declination.cos -
|
|
154
|
+
other.declination.cos * @declination.sin *
|
|
155
|
+
delta_right_ascension.cos
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
end
|
|
103
159
|
end
|
|
104
160
|
end
|
|
105
161
|
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:,
|
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
|
|