astronoby 0.6.0 → 0.8.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 -0
- data/.standard.yml +1 -0
- data/CHANGELOG.md +203 -3
- data/README.md +69 -288
- data/UPGRADING.md +267 -0
- data/docs/README.md +196 -0
- data/docs/angles.md +137 -0
- data/docs/celestial_bodies.md +107 -0
- data/docs/configuration.md +98 -0
- data/docs/coordinates.md +167 -0
- data/docs/ephem.md +85 -0
- data/docs/equinoxes_solstices_times.md +31 -0
- data/docs/glossary.md +152 -0
- data/docs/instant.md +139 -0
- data/docs/moon_phases.md +79 -0
- data/docs/observer.md +65 -0
- data/docs/reference_frames.md +138 -0
- data/docs/rise_transit_set_times.md +119 -0
- data/docs/twilight_times.md +123 -0
- data/lib/astronoby/aberration.rb +56 -31
- data/lib/astronoby/angle.rb +20 -16
- data/lib/astronoby/angles/dms.rb +2 -2
- data/lib/astronoby/angles/hms.rb +2 -2
- data/lib/astronoby/bodies/earth.rb +62 -0
- data/lib/astronoby/bodies/jupiter.rb +28 -0
- data/lib/astronoby/bodies/mars.rb +28 -0
- data/lib/astronoby/bodies/mercury.rb +32 -0
- data/lib/astronoby/bodies/moon.rb +51 -298
- data/lib/astronoby/bodies/neptune.rb +32 -0
- data/lib/astronoby/bodies/saturn.rb +37 -0
- data/lib/astronoby/bodies/solar_system_body.rb +232 -0
- data/lib/astronoby/bodies/sun.rb +33 -214
- data/lib/astronoby/bodies/uranus.rb +16 -0
- data/lib/astronoby/bodies/venus.rb +36 -0
- data/lib/astronoby/cache.rb +188 -0
- data/lib/astronoby/configuration.rb +92 -0
- data/lib/astronoby/constants.rb +17 -2
- data/lib/astronoby/constellation.rb +12 -0
- data/lib/astronoby/constellations/data.rb +42 -0
- data/lib/astronoby/constellations/finder.rb +35 -0
- data/lib/astronoby/constellations/repository.rb +20 -0
- data/lib/astronoby/coordinates/ecliptic.rb +2 -37
- data/lib/astronoby/coordinates/equatorial.rb +28 -10
- data/lib/astronoby/coordinates/horizontal.rb +0 -46
- data/lib/astronoby/corrections/light_time_delay.rb +90 -0
- data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
- data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
- data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
- data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
- data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
- data/lib/astronoby/deflection.rb +187 -0
- data/lib/astronoby/distance.rb +9 -0
- data/lib/astronoby/ephem.rb +39 -0
- data/lib/astronoby/equinox_solstice.rb +22 -19
- data/lib/astronoby/errors.rb +4 -0
- data/lib/astronoby/events/moon_phases.rb +15 -13
- data/lib/astronoby/events/rise_transit_set_calculator.rb +376 -0
- data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
- data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
- data/lib/astronoby/events/twilight_calculator.rb +221 -0
- data/lib/astronoby/events/twilight_event.rb +28 -0
- data/lib/astronoby/events/twilight_events.rb +22 -115
- data/lib/astronoby/instant.rb +176 -0
- data/lib/astronoby/julian_date.rb +78 -0
- data/lib/astronoby/mean_obliquity.rb +24 -13
- data/lib/astronoby/nutation.rb +235 -42
- data/lib/astronoby/observer.rb +55 -0
- data/lib/astronoby/precession.rb +102 -18
- data/lib/astronoby/reference_frame.rb +50 -0
- data/lib/astronoby/reference_frames/apparent.rb +60 -0
- data/lib/astronoby/reference_frames/astrometric.rb +21 -0
- data/lib/astronoby/reference_frames/geometric.rb +20 -0
- data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
- data/lib/astronoby/reference_frames/topocentric.rb +72 -0
- data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
- data/lib/astronoby/true_obliquity.rb +3 -3
- data/lib/astronoby/util/maths.rb +70 -73
- data/lib/astronoby/util/time.rb +455 -32
- data/lib/astronoby/vector.rb +36 -0
- data/lib/astronoby/velocity.rb +116 -0
- data/lib/astronoby/version.rb +1 -1
- data/lib/astronoby.rb +33 -5
- metadata +117 -24
- data/.tool-versions +0 -1
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -80
- data/benchmark/README.md +0 -131
- data/benchmark/benchmark.rb +0 -259
- data/benchmark/data/imcce.csv.zip +0 -0
- data/benchmark/data/sun_calc.csv.zip +0 -0
- data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
- data/lib/astronoby/epoch.rb +0 -22
- data/lib/astronoby/events/observation_events.rb +0 -285
- data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -218
- data/lib/astronoby/util/astrodynamics.rb +0 -60
@@ -0,0 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "monitor"
|
4
|
+
require "singleton"
|
5
|
+
|
6
|
+
module Astronoby
|
7
|
+
class Cache
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
Entry = Struct.new(:key, :value, :prev, :next)
|
11
|
+
|
12
|
+
DEFAULT_MAX_SIZE = 10_000
|
13
|
+
|
14
|
+
attr_reader :max_size
|
15
|
+
|
16
|
+
# @param max_size [Integer] Maximum number of entries allowed in the cache
|
17
|
+
# @return [void]
|
18
|
+
def initialize(max_size = DEFAULT_MAX_SIZE)
|
19
|
+
@max_size = max_size
|
20
|
+
@hash = {}
|
21
|
+
@head = nil
|
22
|
+
@tail = nil
|
23
|
+
@mutex = Monitor.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param key [Object] the cache key
|
27
|
+
# @return [Object, nil] the value, or nil if not found
|
28
|
+
def [](key)
|
29
|
+
@mutex.synchronize do
|
30
|
+
entry = @hash[key]
|
31
|
+
if entry
|
32
|
+
move_to_head(entry)
|
33
|
+
entry.value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @param key [Object]
|
39
|
+
# @param value [Object]
|
40
|
+
# @return [Object] the value set
|
41
|
+
def []=(key, value)
|
42
|
+
@mutex.synchronize do
|
43
|
+
entry = @hash[key]
|
44
|
+
if entry
|
45
|
+
entry.value = value
|
46
|
+
move_to_head(entry)
|
47
|
+
else
|
48
|
+
entry = Entry.new(key, value)
|
49
|
+
add_to_head(entry)
|
50
|
+
@hash[key] = entry
|
51
|
+
evict_last_recently_used if @hash.size > @max_size
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param key [Object]
|
57
|
+
# @yieldreturn [Object] Value to store if key is missing.
|
58
|
+
# @return [Object] Cached or computed value.
|
59
|
+
def fetch(key)
|
60
|
+
return self[key] if @mutex.synchronize { @hash.key?(key) }
|
61
|
+
|
62
|
+
value = yield
|
63
|
+
|
64
|
+
@mutex.synchronize do
|
65
|
+
self[key] = value unless @hash.key?(key)
|
66
|
+
end
|
67
|
+
|
68
|
+
value
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [void]
|
72
|
+
def clear
|
73
|
+
@mutex.synchronize do
|
74
|
+
@hash.clear
|
75
|
+
@head = @tail = nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Integer]
|
80
|
+
def size
|
81
|
+
@mutex.synchronize { @hash.size }
|
82
|
+
end
|
83
|
+
|
84
|
+
# @param new_size [Integer] the new cache limit.
|
85
|
+
# @return [void]
|
86
|
+
def max_size=(new_size)
|
87
|
+
raise ArgumentError, "max_size must be positive" unless new_size > 0
|
88
|
+
@mutex.synchronize do
|
89
|
+
@max_size = new_size
|
90
|
+
while @hash.size > @max_size
|
91
|
+
evict_last_recently_used
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def add_to_head(entry)
|
99
|
+
entry.prev = nil
|
100
|
+
entry.next = @head
|
101
|
+
@head.prev = entry if @head
|
102
|
+
@head = entry
|
103
|
+
@tail ||= entry
|
104
|
+
end
|
105
|
+
|
106
|
+
def move_to_head(entry)
|
107
|
+
return if @head == entry
|
108
|
+
|
109
|
+
# Unlink
|
110
|
+
entry.prev.next = entry.next if entry.prev
|
111
|
+
entry.next.prev = entry.prev if entry.next
|
112
|
+
|
113
|
+
# Update tail if needed
|
114
|
+
@tail = entry.prev if @tail == entry
|
115
|
+
|
116
|
+
# Insert as new head
|
117
|
+
entry.prev = nil
|
118
|
+
entry.next = @head
|
119
|
+
@head.prev = entry if @head
|
120
|
+
@head = entry
|
121
|
+
end
|
122
|
+
|
123
|
+
def evict_last_recently_used
|
124
|
+
if @tail
|
125
|
+
@hash.delete(@tail.key)
|
126
|
+
if @tail.prev
|
127
|
+
@tail = @tail.prev
|
128
|
+
@tail.next = nil
|
129
|
+
else
|
130
|
+
@head = @tail = nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class NullCache
|
137
|
+
include Singleton
|
138
|
+
|
139
|
+
def [](key)
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
def []=(key, value)
|
144
|
+
value
|
145
|
+
end
|
146
|
+
|
147
|
+
def fetch(key)
|
148
|
+
yield
|
149
|
+
end
|
150
|
+
|
151
|
+
def clear
|
152
|
+
end
|
153
|
+
|
154
|
+
def size
|
155
|
+
0
|
156
|
+
end
|
157
|
+
|
158
|
+
def max_size=(new_size)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
class CacheKey
|
163
|
+
class << self
|
164
|
+
# Generate a cache key with appropriate precision
|
165
|
+
# @param type [Symbol] The calculation type (e.g., :geometric, :nutation)
|
166
|
+
# @param instant [Astronoby::Instant] The time instant
|
167
|
+
# @param components [Array] Additional components for the key
|
168
|
+
# @return [Array] The complete cache key
|
169
|
+
def generate(type, instant, *components)
|
170
|
+
return nil unless Astronoby.configuration.cache_enabled?
|
171
|
+
|
172
|
+
precision = Astronoby.configuration.cache_precision(type)
|
173
|
+
rounded_tt = round_terrestrial_time(instant.tt, precision)
|
174
|
+
[type, rounded_tt, *components]
|
175
|
+
end
|
176
|
+
|
177
|
+
# Round terrestrial time to specified precision
|
178
|
+
# @param tt [Numeric] Terrestrial time value
|
179
|
+
# @param precision [Numeric] Precision in seconds
|
180
|
+
# @return [Numeric] Rounded time value
|
181
|
+
def round_terrestrial_time(tt, precision)
|
182
|
+
return tt if precision <= 0
|
183
|
+
|
184
|
+
tt.round(precision)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
class Configuration
|
5
|
+
DEFAULT_PRECISIONS = {
|
6
|
+
geometric: 9, # 8.64e-5 seconds
|
7
|
+
nutation: 2, # 864 seconds
|
8
|
+
precession: 2, # 864 seconds
|
9
|
+
rise_transit_set: 5 # 0.1 seconds
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
attr_accessor :cache_enabled
|
13
|
+
attr_reader :cache_precisions
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@cache_enabled = false
|
17
|
+
@cache_precisions = DEFAULT_PRECISIONS.dup
|
18
|
+
@cache_instance = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
# Configure cache precision for specific calculation types
|
22
|
+
# @param type [Symbol] The calculation type
|
23
|
+
# @param precision [Numeric] Precision in rounding of terrestrial time
|
24
|
+
# @return [Numeric] the precision for the given type
|
25
|
+
def cache_precision(type, precision = nil)
|
26
|
+
if precision.nil?
|
27
|
+
@cache_precisions[type] || DEFAULT_PRECISIONS[:geometric]
|
28
|
+
else
|
29
|
+
@cache_precisions[type] = precision
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set all cache precisions at once
|
34
|
+
# @param precisions [Hash] Hash of calculation type to precision in rounding
|
35
|
+
# of terrestrial time
|
36
|
+
# @return [void]
|
37
|
+
def cache_precisions=(precisions)
|
38
|
+
@cache_precisions = DEFAULT_PRECISIONS.merge(precisions)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get the cache instance (singleton or null cache)
|
42
|
+
# @return [Astronoby::Cache, Astronoby::NullCache] the cache instance
|
43
|
+
def cache
|
44
|
+
if cache_enabled?
|
45
|
+
Cache.instance
|
46
|
+
else
|
47
|
+
NullCache.instance
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def cache_enabled?
|
52
|
+
@cache_enabled
|
53
|
+
end
|
54
|
+
|
55
|
+
# Reset cache instance (useful for testing or configuration changes)
|
56
|
+
# @return [void]
|
57
|
+
def reset_cache!
|
58
|
+
cache.clear
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class << self
|
63
|
+
# Global configuration instance
|
64
|
+
# @return [Astronoby::Configuration] the global configuration instance
|
65
|
+
def configuration
|
66
|
+
@configuration ||= Configuration.new
|
67
|
+
end
|
68
|
+
|
69
|
+
# Configuration block for setup
|
70
|
+
# @example
|
71
|
+
# Astronoby.configure do |config|
|
72
|
+
# config.cache_enabled = false
|
73
|
+
# config.cache_precision(:geometric, 9)
|
74
|
+
# end
|
75
|
+
def configure
|
76
|
+
yield(configuration)
|
77
|
+
configuration.reset_cache!
|
78
|
+
end
|
79
|
+
|
80
|
+
# Quick access to cache
|
81
|
+
# @return [Astronoby::Cache, Astronoby::NullCache] the cache instance
|
82
|
+
def cache
|
83
|
+
configuration.cache
|
84
|
+
end
|
85
|
+
|
86
|
+
# Reset configuration to defaults
|
87
|
+
# @return [void]
|
88
|
+
def reset_configuration!
|
89
|
+
@configuration = Configuration.new
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/astronoby/constants.rb
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
|
3
3
|
module Astronoby
|
4
4
|
class Constants
|
5
|
-
|
5
|
+
DAYS_PER_JULIAN_YEAR = 365.25
|
6
|
+
DAYS_PER_JULIAN_CENTURY = DAYS_PER_JULIAN_YEAR * 100
|
6
7
|
DAYS_PER_JULIAN_MILLENIA = DAYS_PER_JULIAN_CENTURY * 10
|
7
8
|
|
9
|
+
TROPICAL_YEAR_AT_B1900 = 365.242198781
|
10
|
+
|
8
11
|
HOURS_PER_DAY = 24.0
|
9
12
|
DEGREES_PER_CIRCLE = 360.0
|
10
13
|
RADIANS_PER_CIRCLE = 2 * Math::PI
|
@@ -15,8 +18,8 @@ module Astronoby
|
|
15
18
|
|
16
19
|
SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR
|
17
20
|
SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY
|
18
|
-
SECONDS_PER_DEGREE = SECONDS_PER_MINUTE * MINUTES_PER_DEGREE
|
19
21
|
RADIAN_PER_HOUR = Math::PI / 12.0
|
22
|
+
MICROSECOND_IN_DAYS = 1.0 / SECONDS_PER_DAY / 1e6
|
20
23
|
|
21
24
|
PI_IN_DEGREES = 180.0
|
22
25
|
|
@@ -26,6 +29,18 @@ module Astronoby
|
|
26
29
|
ASTRONOMICAL_UNIT_IN_METERS = 149_597_870_700
|
27
30
|
EARTH_EQUATORIAL_RADIUS_IN_METERS = 6378140
|
28
31
|
|
32
|
+
# WGS84 Earth Constants
|
33
|
+
WGS84_EARTH_EQUATORIAL_RADIUS_IN_METERS = 6378137
|
34
|
+
WGS84_INVERSE_FLATTENING = 298.257223563
|
35
|
+
WGS84_FLATTENING = 1.0 / WGS84_INVERSE_FLATTENING
|
36
|
+
WGS84_ECCENTICITY_SQUARED = 2.0 * WGS84_FLATTENING - WGS84_FLATTENING * WGS84_FLATTENING
|
37
|
+
|
38
|
+
LIGHT_SPEED_M_PER_S = 299_792_458
|
39
|
+
|
29
40
|
EARTH_FLATTENING_CORRECTION = 0.996647
|
41
|
+
|
42
|
+
EARTH_ANGULAR_VELOCITY_RAD_PER_S = 7.2921159e-5
|
43
|
+
|
44
|
+
TAI_TT_OFFSET = 32.184
|
30
45
|
end
|
31
46
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Constellations
|
5
|
+
class Data
|
6
|
+
def self.sorted_right_ascensions
|
7
|
+
@sorted_right_ascensions ||=
|
8
|
+
read_file("sorted_right_ascensions.dat").map(&:to_f)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.sorted_declinations
|
12
|
+
@sorted_declinations ||=
|
13
|
+
read_file("sorted_declinations.dat").map(&:to_f)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.radec_to_index
|
17
|
+
@radec_to_index ||=
|
18
|
+
read_file("radec_to_index.dat").map do |line|
|
19
|
+
line.split.map(&:to_i)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.indexed_abbreviations
|
24
|
+
@indexed_abbreviations ||=
|
25
|
+
read_file("indexed_abbreviations.dat")
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.constellation_names
|
29
|
+
@constellation_names ||=
|
30
|
+
read_file("constellation_names.dat").map(&:split)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.read_file(filename)
|
34
|
+
File.readlines(data_path(filename)).map(&:strip)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.data_path(filename)
|
38
|
+
File.join(__dir__, "..", "data", "constellations", filename)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Constellations
|
5
|
+
class Finder
|
6
|
+
# Finds the constellation that contains the given B1875 equatorial
|
7
|
+
# coordinates.
|
8
|
+
# @param equatorial_coordinates [Astronoby::Coordinates::Equatorial] The
|
9
|
+
# B1875 coordinates to search for.
|
10
|
+
# @return [Astronoby::Constellation, nil] The constellation that contains
|
11
|
+
# the coordinates, or nil if none is found.
|
12
|
+
def self.find(equatorial_coordinates)
|
13
|
+
ra_hours = equatorial_coordinates.right_ascension.hours
|
14
|
+
dec_degrees = equatorial_coordinates.declination.degrees
|
15
|
+
ra_index = Data
|
16
|
+
.sorted_right_ascensions
|
17
|
+
.bsearch_index { _1 >= ra_hours }
|
18
|
+
dec_index = Data
|
19
|
+
.sorted_declinations
|
20
|
+
.bsearch_index { _1 > dec_degrees }
|
21
|
+
|
22
|
+
return if ra_index.nil? || dec_index.nil?
|
23
|
+
|
24
|
+
abbreviation_index = Data.radec_to_index.dig(ra_index, dec_index)
|
25
|
+
|
26
|
+
return if abbreviation_index.nil?
|
27
|
+
|
28
|
+
constellation_abbreviation =
|
29
|
+
Data.indexed_abbreviations[abbreviation_index]
|
30
|
+
|
31
|
+
Repository.get(constellation_abbreviation)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Constellations
|
5
|
+
class Repository
|
6
|
+
# Returns a constellation object for the given abbreviation.
|
7
|
+
# @param abbreviation [String] The abbreviation of the constellation.
|
8
|
+
# @return [Astronoby::Constellation, nil] The constellation object or nil
|
9
|
+
# if not found.
|
10
|
+
def self.get(abbreviation)
|
11
|
+
@constellations ||= Data.constellation_names.map do |abbr, name|
|
12
|
+
constellation = Constellation.new(name, abbr)
|
13
|
+
[abbr, constellation]
|
14
|
+
end.to_h
|
15
|
+
|
16
|
+
@constellations[abbreviation]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -10,43 +10,8 @@ module Astronoby
|
|
10
10
|
@longitude = longitude
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
# Author: J. L. Lawrence
|
16
|
-
# Edition: MIT Press
|
17
|
-
# Chapter: 4 - Orbits and Coordinate Systems
|
18
|
-
|
19
|
-
def to_true_equatorial(epoch:)
|
20
|
-
mean_obliquity = MeanObliquity.for_epoch(epoch)
|
21
|
-
to_equatorial(obliquity: mean_obliquity, epoch: epoch)
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_apparent_equatorial(epoch:)
|
25
|
-
apparent_obliquity = TrueObliquity.for_epoch(epoch)
|
26
|
-
to_equatorial(obliquity: apparent_obliquity, epoch: epoch)
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def to_equatorial(obliquity:, epoch:)
|
32
|
-
y = Angle.from_radians(
|
33
|
-
@longitude.sin * obliquity.cos -
|
34
|
-
@latitude.tan * obliquity.sin
|
35
|
-
)
|
36
|
-
x = Angle.from_radians(@longitude.cos)
|
37
|
-
r = Angle.atan(y.radians / x.radians)
|
38
|
-
right_ascension = Util::Trigonometry.adjustement_for_arctangent(y, x, r)
|
39
|
-
|
40
|
-
declination = Angle.asin(
|
41
|
-
@latitude.sin * obliquity.cos +
|
42
|
-
@latitude.cos * obliquity.sin * @longitude.sin
|
43
|
-
)
|
44
|
-
|
45
|
-
Equatorial.new(
|
46
|
-
right_ascension: right_ascension,
|
47
|
-
declination: declination,
|
48
|
-
epoch: epoch
|
49
|
-
)
|
13
|
+
def self.zero
|
14
|
+
new(latitude: Angle.zero, longitude: Angle.zero)
|
50
15
|
end
|
51
16
|
end
|
52
17
|
end
|
@@ -9,7 +9,7 @@ module Astronoby
|
|
9
9
|
declination:,
|
10
10
|
right_ascension:,
|
11
11
|
hour_angle: nil,
|
12
|
-
epoch:
|
12
|
+
epoch: JulianDate::DEFAULT_EPOCH
|
13
13
|
)
|
14
14
|
@right_ascension = right_ascension
|
15
15
|
@declination = declination
|
@@ -17,6 +17,30 @@ module Astronoby
|
|
17
17
|
@epoch = epoch
|
18
18
|
end
|
19
19
|
|
20
|
+
def self.zero
|
21
|
+
new(declination: Angle.zero, right_ascension: Angle.zero)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.from_position_vector(position)
|
25
|
+
return zero if position.zero?
|
26
|
+
|
27
|
+
term1 = position.z.m
|
28
|
+
term2 = position.magnitude.m
|
29
|
+
declination = Angle.asin(term1 / term2)
|
30
|
+
|
31
|
+
term1 = position.y.m
|
32
|
+
term2 = position.x.m
|
33
|
+
angle = Angle.atan(term1 / term2)
|
34
|
+
right_ascension =
|
35
|
+
Astronoby::Util::Trigonometry.adjustement_for_arctangent(
|
36
|
+
term1,
|
37
|
+
term2,
|
38
|
+
angle
|
39
|
+
)
|
40
|
+
|
41
|
+
new(declination: declination, right_ascension: right_ascension)
|
42
|
+
end
|
43
|
+
|
20
44
|
def compute_hour_angle(time:, longitude:)
|
21
45
|
lst = GreenwichSiderealTime
|
22
46
|
.from_utc(time.utc)
|
@@ -38,6 +62,7 @@ module Astronoby
|
|
38
62
|
|
39
63
|
t1 = @declination.sin - latitude.sin * altitude.sin
|
40
64
|
t2 = t1 / (latitude.cos * altitude.cos)
|
65
|
+
t2 = t2.clamp(-1, 1)
|
41
66
|
azimuth = Angle.acos(t2)
|
42
67
|
|
43
68
|
if ha.sin.positive?
|
@@ -57,8 +82,8 @@ module Astronoby
|
|
57
82
|
# Author: J. L. Lawrence
|
58
83
|
# Edition: MIT Press
|
59
84
|
# Chapter: 4 - Orbits and Coordinate Systems
|
60
|
-
def to_ecliptic(
|
61
|
-
mean_obliquity = MeanObliquity.
|
85
|
+
def to_ecliptic(instant:)
|
86
|
+
mean_obliquity = MeanObliquity.at(instant)
|
62
87
|
|
63
88
|
y = Angle.from_radians(
|
64
89
|
@right_ascension.sin * mean_obliquity.cos +
|
@@ -78,13 +103,6 @@ module Astronoby
|
|
78
103
|
longitude: longitude
|
79
104
|
)
|
80
105
|
end
|
81
|
-
|
82
|
-
def to_epoch(epoch)
|
83
|
-
Precession.for_equatorial_coordinates(
|
84
|
-
coordinates: self,
|
85
|
-
epoch: epoch
|
86
|
-
)
|
87
|
-
end
|
88
106
|
end
|
89
107
|
end
|
90
108
|
end
|
@@ -14,52 +14,6 @@ module Astronoby
|
|
14
14
|
@altitude = altitude
|
15
15
|
@observer = observer
|
16
16
|
end
|
17
|
-
|
18
|
-
def to_equatorial(time:)
|
19
|
-
t0 = @altitude.sin * latitude.sin +
|
20
|
-
@altitude.cos * latitude.cos * @azimuth.cos
|
21
|
-
|
22
|
-
declination = Angle.asin(t0)
|
23
|
-
|
24
|
-
t1 = @altitude.sin - latitude.sin * declination.sin
|
25
|
-
|
26
|
-
hour_angle_degrees = Angle
|
27
|
-
.acos(t1 / (latitude.cos * declination.cos))
|
28
|
-
.degrees
|
29
|
-
|
30
|
-
if @azimuth.sin.positive?
|
31
|
-
hour_angle_degrees = Angle
|
32
|
-
.from_degrees(Constants::DEGREES_PER_CIRCLE - hour_angle_degrees)
|
33
|
-
.degrees
|
34
|
-
end
|
35
|
-
|
36
|
-
hour_angle_hours = Angle.from_degrees(hour_angle_degrees).hours
|
37
|
-
lst = GreenwichSiderealTime
|
38
|
-
.from_utc(time.utc)
|
39
|
-
.to_lst(longitude: longitude)
|
40
|
-
right_ascension_decimal = lst.time - hour_angle_hours
|
41
|
-
|
42
|
-
if right_ascension_decimal.negative?
|
43
|
-
right_ascension_decimal += Constants::HOURS_PER_DAY
|
44
|
-
end
|
45
|
-
|
46
|
-
right_ascension = Angle.from_hours(right_ascension_decimal)
|
47
|
-
|
48
|
-
Equatorial.new(
|
49
|
-
right_ascension: right_ascension,
|
50
|
-
declination: declination
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def latitude
|
57
|
-
@observer.latitude
|
58
|
-
end
|
59
|
-
|
60
|
-
def longitude
|
61
|
-
@observer.longitude
|
62
|
-
end
|
63
17
|
end
|
64
18
|
end
|
65
19
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Astronoby
|
4
|
+
module Correction
|
5
|
+
class LightTimeDelay
|
6
|
+
LIGHT_SPEED_CORRECTION_MAXIMUM_ITERATIONS = 10
|
7
|
+
LIGHT_SPEED_CORRECTION_PRECISION = 1e-12
|
8
|
+
|
9
|
+
def self.compute(center:, target:, ephem:)
|
10
|
+
new(center: center, target: target, ephem: ephem).compute
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(center:, target:, ephem:)
|
14
|
+
@center = center
|
15
|
+
@target = target
|
16
|
+
@ephem = ephem
|
17
|
+
end
|
18
|
+
|
19
|
+
def delay
|
20
|
+
@_delay ||= begin
|
21
|
+
compute
|
22
|
+
@delay
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def compute
|
27
|
+
@corrected_position ||= begin
|
28
|
+
corrected_position = Vector[
|
29
|
+
Distance.zero,
|
30
|
+
Distance.zero,
|
31
|
+
Distance.zero
|
32
|
+
]
|
33
|
+
corrected_velocity = Vector[
|
34
|
+
Velocity.zero,
|
35
|
+
Velocity.zero,
|
36
|
+
Velocity.zero
|
37
|
+
]
|
38
|
+
@delay = initial_delta_in_seconds
|
39
|
+
|
40
|
+
LIGHT_SPEED_CORRECTION_MAXIMUM_ITERATIONS.times do
|
41
|
+
new_instant = Instant.from_terrestrial_time(
|
42
|
+
instant.tt - @delay / Constants::SECONDS_PER_DAY
|
43
|
+
)
|
44
|
+
corrected = @target
|
45
|
+
.target_body
|
46
|
+
.geometric(ephem: @ephem, instant: new_instant)
|
47
|
+
corrected_position = corrected.position
|
48
|
+
corrected_velocity = corrected.velocity
|
49
|
+
|
50
|
+
corrected_distance_in_km = Math.sqrt(
|
51
|
+
(corrected_position.x.km - position.x.km)**2 +
|
52
|
+
(corrected_position.y.km - position.y.km)**2 +
|
53
|
+
(corrected_position.z.km - position.z.km)**2
|
54
|
+
)
|
55
|
+
|
56
|
+
new_delay = corrected_distance_in_km / Velocity.light_speed.kmps
|
57
|
+
|
58
|
+
break if (new_delay - @delay).abs < LIGHT_SPEED_CORRECTION_PRECISION
|
59
|
+
|
60
|
+
@delay = new_delay
|
61
|
+
end
|
62
|
+
|
63
|
+
[corrected_position, corrected_velocity]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def position
|
70
|
+
@position ||= @center.position
|
71
|
+
end
|
72
|
+
|
73
|
+
def instant
|
74
|
+
@center.instant
|
75
|
+
end
|
76
|
+
|
77
|
+
def initial_delta_in_seconds
|
78
|
+
distance_between_bodies_in_km / Velocity.light_speed.kmps
|
79
|
+
end
|
80
|
+
|
81
|
+
def distance_between_bodies_in_km
|
82
|
+
Math.sqrt(
|
83
|
+
(@target.position.x.km - position.x.km)**2 +
|
84
|
+
(@target.position.y.km - position.y.km)**2 +
|
85
|
+
(@target.position.z.km - position.z.km)**2
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|