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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +203 -3
  5. data/README.md +69 -288
  6. data/UPGRADING.md +267 -0
  7. data/docs/README.md +196 -0
  8. data/docs/angles.md +137 -0
  9. data/docs/celestial_bodies.md +107 -0
  10. data/docs/configuration.md +98 -0
  11. data/docs/coordinates.md +167 -0
  12. data/docs/ephem.md +85 -0
  13. data/docs/equinoxes_solstices_times.md +31 -0
  14. data/docs/glossary.md +152 -0
  15. data/docs/instant.md +139 -0
  16. data/docs/moon_phases.md +79 -0
  17. data/docs/observer.md +65 -0
  18. data/docs/reference_frames.md +138 -0
  19. data/docs/rise_transit_set_times.md +119 -0
  20. data/docs/twilight_times.md +123 -0
  21. data/lib/astronoby/aberration.rb +56 -31
  22. data/lib/astronoby/angle.rb +20 -16
  23. data/lib/astronoby/angles/dms.rb +2 -2
  24. data/lib/astronoby/angles/hms.rb +2 -2
  25. data/lib/astronoby/bodies/earth.rb +62 -0
  26. data/lib/astronoby/bodies/jupiter.rb +28 -0
  27. data/lib/astronoby/bodies/mars.rb +28 -0
  28. data/lib/astronoby/bodies/mercury.rb +32 -0
  29. data/lib/astronoby/bodies/moon.rb +51 -298
  30. data/lib/astronoby/bodies/neptune.rb +32 -0
  31. data/lib/astronoby/bodies/saturn.rb +37 -0
  32. data/lib/astronoby/bodies/solar_system_body.rb +232 -0
  33. data/lib/astronoby/bodies/sun.rb +33 -214
  34. data/lib/astronoby/bodies/uranus.rb +16 -0
  35. data/lib/astronoby/bodies/venus.rb +36 -0
  36. data/lib/astronoby/cache.rb +188 -0
  37. data/lib/astronoby/configuration.rb +92 -0
  38. data/lib/astronoby/constants.rb +17 -2
  39. data/lib/astronoby/constellation.rb +12 -0
  40. data/lib/astronoby/constellations/data.rb +42 -0
  41. data/lib/astronoby/constellations/finder.rb +35 -0
  42. data/lib/astronoby/constellations/repository.rb +20 -0
  43. data/lib/astronoby/coordinates/ecliptic.rb +2 -37
  44. data/lib/astronoby/coordinates/equatorial.rb +28 -10
  45. data/lib/astronoby/coordinates/horizontal.rb +0 -46
  46. data/lib/astronoby/corrections/light_time_delay.rb +90 -0
  47. data/lib/astronoby/data/constellations/constellation_names.dat +88 -0
  48. data/lib/astronoby/data/constellations/indexed_abbreviations.dat +88 -0
  49. data/lib/astronoby/data/constellations/radec_to_index.dat +238 -0
  50. data/lib/astronoby/data/constellations/sorted_declinations.dat +202 -0
  51. data/lib/astronoby/data/constellations/sorted_right_ascensions.dat +237 -0
  52. data/lib/astronoby/deflection.rb +187 -0
  53. data/lib/astronoby/distance.rb +9 -0
  54. data/lib/astronoby/ephem.rb +39 -0
  55. data/lib/astronoby/equinox_solstice.rb +22 -19
  56. data/lib/astronoby/errors.rb +4 -0
  57. data/lib/astronoby/events/moon_phases.rb +15 -13
  58. data/lib/astronoby/events/rise_transit_set_calculator.rb +376 -0
  59. data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
  60. data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
  61. data/lib/astronoby/events/twilight_calculator.rb +221 -0
  62. data/lib/astronoby/events/twilight_event.rb +28 -0
  63. data/lib/astronoby/events/twilight_events.rb +22 -115
  64. data/lib/astronoby/instant.rb +176 -0
  65. data/lib/astronoby/julian_date.rb +78 -0
  66. data/lib/astronoby/mean_obliquity.rb +24 -13
  67. data/lib/astronoby/nutation.rb +235 -42
  68. data/lib/astronoby/observer.rb +55 -0
  69. data/lib/astronoby/precession.rb +102 -18
  70. data/lib/astronoby/reference_frame.rb +50 -0
  71. data/lib/astronoby/reference_frames/apparent.rb +60 -0
  72. data/lib/astronoby/reference_frames/astrometric.rb +21 -0
  73. data/lib/astronoby/reference_frames/geometric.rb +20 -0
  74. data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
  75. data/lib/astronoby/reference_frames/topocentric.rb +72 -0
  76. data/lib/astronoby/time/greenwich_sidereal_time.rb +2 -2
  77. data/lib/astronoby/true_obliquity.rb +3 -3
  78. data/lib/astronoby/util/maths.rb +70 -73
  79. data/lib/astronoby/util/time.rb +455 -32
  80. data/lib/astronoby/vector.rb +36 -0
  81. data/lib/astronoby/velocity.rb +116 -0
  82. data/lib/astronoby/version.rb +1 -1
  83. data/lib/astronoby.rb +33 -5
  84. metadata +117 -24
  85. data/.tool-versions +0 -1
  86. data/Gemfile +0 -5
  87. data/Gemfile.lock +0 -80
  88. data/benchmark/README.md +0 -131
  89. data/benchmark/benchmark.rb +0 -259
  90. data/benchmark/data/imcce.csv.zip +0 -0
  91. data/benchmark/data/sun_calc.csv.zip +0 -0
  92. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
  93. data/lib/astronoby/epoch.rb +0 -22
  94. data/lib/astronoby/events/observation_events.rb +0 -285
  95. data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -218
  96. 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
@@ -2,9 +2,12 @@
2
2
 
3
3
  module Astronoby
4
4
  class Constants
5
- DAYS_PER_JULIAN_CENTURY = 36525.0
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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Constellation
5
+ attr_reader :name, :abbreviation
6
+
7
+ def initialize(name, abbreviation)
8
+ @name = name
9
+ @abbreviation = abbreviation
10
+ end
11
+ end
12
+ 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
- # Source:
14
- # Title: Celestial Calculations
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: Epoch::DEFAULT_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(epoch:)
61
- mean_obliquity = MeanObliquity.for_epoch(epoch)
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