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,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class MeanOfDate < ReferenceFrame
5
+ def self.build_from_geometric(
6
+ instant:,
7
+ target_geometric:,
8
+ earth_geometric:,
9
+ target_body:
10
+ )
11
+ position = target_geometric.position - earth_geometric.position
12
+ velocity = target_geometric.velocity - earth_geometric.velocity
13
+ precession_matrix = Precession.matrix_for(instant)
14
+ corrected_position = Distance.vector_from_m(
15
+ precession_matrix * position.map(&:m)
16
+ )
17
+ corrected_velocity = Velocity.vector_from_mps(
18
+ precession_matrix * velocity.map(&:mps)
19
+ )
20
+
21
+ new(
22
+ position: corrected_position,
23
+ velocity: corrected_velocity,
24
+ instant: instant,
25
+ center_identifier: SolarSystemBody::EARTH,
26
+ target_body: target_body
27
+ )
28
+ end
29
+
30
+ def ecliptic
31
+ @ecliptic ||= begin
32
+ return Coordinates::Ecliptic.zero if distance.zero?
33
+
34
+ equatorial.to_ecliptic(instant: @instant)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Astronoby
4
+ class Topocentric < ReferenceFrame
5
+ def self.build_from_apparent(
6
+ apparent:,
7
+ observer:,
8
+ instant:,
9
+ target_body:
10
+ )
11
+ matrix = observer.earth_fixed_rotation_matrix_for(instant)
12
+
13
+ observer_position = Distance.vector_from_meters(
14
+ matrix * observer.geocentric_position.map(&:m)
15
+ )
16
+ observer_velocity = Velocity.vector_from_mps(
17
+ matrix * observer.geocentric_velocity.map(&:kmps)
18
+ )
19
+
20
+ position = apparent.position - observer_position
21
+ velocity = apparent.velocity - observer_velocity
22
+
23
+ new(
24
+ position: position,
25
+ velocity: velocity,
26
+ instant: instant,
27
+ center_identifier: [observer.longitude, observer.latitude],
28
+ target_body: target_body,
29
+ observer: observer
30
+ )
31
+ end
32
+
33
+ def initialize(
34
+ position:,
35
+ velocity:,
36
+ instant:,
37
+ center_identifier:,
38
+ target_body:,
39
+ observer:
40
+ )
41
+ super(
42
+ position: position,
43
+ velocity: velocity,
44
+ instant: instant,
45
+ center_identifier: center_identifier,
46
+ target_body: target_body
47
+ )
48
+ @observer = observer
49
+ end
50
+
51
+ def ecliptic
52
+ @ecliptic ||= begin
53
+ return Coordinates::Ecliptic.zero if distance.zero?
54
+
55
+ equatorial.to_ecliptic(instant: @instant)
56
+ end
57
+ end
58
+
59
+ def horizontal(refraction: false)
60
+ horizontal = equatorial.to_horizontal(
61
+ time: @instant.to_time,
62
+ observer: @observer
63
+ )
64
+
65
+ if refraction
66
+ Refraction.correct_horizontal_coordinates(coordinates: horizontal)
67
+ else
68
+ horizontal
69
+ end
70
+ end
71
+ end
72
+ end
@@ -20,7 +20,7 @@ module Astronoby
20
20
  def self.from_utc(utc)
21
21
  date = utc.to_date
22
22
  julian_day = utc.to_date.ajd
23
- t = (julian_day - Epoch::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
23
+ t = (julian_day - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
24
24
  t0 = (
25
25
  (JULIAN_CENTURIES_EXPONENTS[0] +
26
26
  (JULIAN_CENTURIES_EXPONENTS[1] * t) +
@@ -51,7 +51,7 @@ module Astronoby
51
51
  def to_utc
52
52
  date = @date
53
53
  julian_day = @date.ajd
54
- t = (julian_day - Epoch::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
54
+ t = (julian_day - JulianDate::J2000) / Constants::DAYS_PER_JULIAN_CENTURY
55
55
 
56
56
  t0 = (
57
57
  (JULIAN_CENTURIES_EXPONENTS[0] +
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Astronoby
4
4
  class TrueObliquity
5
- def self.for_epoch(epoch)
6
- mean_obliquity = MeanObliquity.for_epoch(epoch)
7
- nutation = Nutation.for_obliquity_of_the_ecliptic(epoch: epoch)
5
+ def self.at(instant)
6
+ mean_obliquity = MeanObliquity.at(instant)
7
+ nutation = Nutation.new(instant: instant).nutation_in_obliquity
8
8
 
9
9
  mean_obliquity + nutation
10
10
  end
@@ -3,91 +3,88 @@
3
3
  module Astronoby
4
4
  module Util
5
5
  module Maths
6
- class << self
7
- # Source:
8
- # Title: Astronomical Algorithms
9
- # Author: Jean Meeus
10
- # Edition: 2nd edition
11
- # Chapter: 3 - Interpolation
6
+ module_function
12
7
 
13
- # @param values [Array<Numeric>] First term
14
- # @param factor [Numeric] Interpolation factor
15
- # @return [Float] Interpolated value
16
- def interpolate(values, factor)
17
- unless factor.between?(0, 1)
18
- raise IncompatibleArgumentsError,
19
- "Interpolation factor must be between 0 and 1, got #{factor}"
20
- end
8
+ def dot_product(a, b)
9
+ a.zip(b).sum { |x, y| x * y }
10
+ end
21
11
 
22
- if values.length == 3
23
- return interpolate_3_terms(values, factor)
24
- elsif values.length == 5
25
- return interpolate_5_terms(values, factor)
26
- end
12
+ # Find maximum altitude using quadratic interpolation
13
+ # @param t1, t2, t3 [Time] Three consecutive times
14
+ # @param alt1, alt2, alt3 [Float] Corresponding altitudes
15
+ # @return [::Time] Time of maximum altitude
16
+ def quadratic_maximum(t1, t2, t3, alt1, alt2, alt3)
17
+ # Convert to float seconds for arithmetic
18
+ x1, x2, x3 = t1.to_f, t2.to_f, t3.to_f
19
+ y1, y2, y3 = alt1, alt2, alt3
20
+
21
+ # Quadratic interpolation formula
22
+ denom = (x1 - x2) * (x1 - x3) * (x2 - x3)
23
+ a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
24
+ b = (x3 * x3 * (y1 - y2) +
25
+ x2 * x2 * (y3 - y1) +
26
+ x1 * x1 * (y2 - y3)) / denom
27
+
28
+ # Maximum is at -b/2a
29
+ max_t = -b / (2 * a)
30
+
31
+ ::Time.at(max_t)
32
+ end
27
33
 
28
- raise IncompatibleArgumentsError,
29
- "Only 3 or 5 terms are supported for interpolation"
34
+ # Linear interpolation between two points
35
+ # @param x1 [Numeric] First x value
36
+ # @param x2 [Numeric] Second x value
37
+ # @param y1 [Numeric] First y value
38
+ # @param y2 [Numeric] Second y value
39
+ # @param target_y [Numeric] Target y value (default: 0)
40
+ # @return [Numeric] Interpolated x value where y=target_y
41
+ def self.linear_interpolate(x1, x2, y1, y2, target_y = 0)
42
+ # Handle horizontal line case (avoid division by zero)
43
+ if (y2 - y1).abs < 1e-10
44
+ # If target_y matches the line's y-value (within precision), return
45
+ # midpoint. Otherwise, return one of the endpoints (no unique solution
46
+ # exists)
47
+ return ((target_y - y1).abs < 1e-10) ? (x1 + x2) / 2.0 : x1
30
48
  end
31
49
 
32
- # Fixes angles forced to be in range [0, 360] or other angle range, for
33
- # interpolation use
34
- # @param angles [Array<Integer|Float>] Angles values
35
- # @param full_circle [Integer] Full circle value
36
- # @return [Array<Interger|Float>] Normalized values
37
- def normalize_angles_for_interpolation(angles, full_circle: 360)
38
- normalized = angles.dup
39
-
40
- (1...normalized.size).each do |i|
41
- prev_angle = normalized[i - 1]
42
-
43
- while normalized[i] - prev_angle > full_circle / 2
44
- normalized[i] -= full_circle
45
- end
46
- while normalized[i] - prev_angle < -full_circle / 2
47
- normalized[i] += full_circle
48
- end
49
- end
50
-
51
- normalized
50
+ # Handle vertical line case
51
+ if (x2 - x1).abs < 1e-10
52
+ # For a vertical line, there's only one x-value possible
53
+ return x1
52
54
  end
53
55
 
54
- private
55
-
56
- # @return [Float] Interpolated value
57
- def interpolate_3_terms(terms, factor)
58
- y1, y2, y3 = terms
59
-
60
- a = y2 - y1
61
- b = y3 - y2
62
- c = b - a
56
+ # Standard linear interpolation formula
57
+ x1 + (target_y - y1) * (x2 - x1) / (y2 - y1)
58
+ end
63
59
 
64
- y2 + (factor / 2.0) * (a + b + factor * c)
60
+ # Creates an array of evenly spaced values between start and stop.
61
+ # @param start [Numeric] The starting value of the sequence
62
+ # @param stop [Numeric] The end value of the sequence
63
+ # @param num [Integer] Number of samples to generate. Default is 50
64
+ # @param endpoint [Boolean] If true, stop is the last sample. Otherwise,
65
+ # it is not included. Default is true
66
+ # @return [Array<Numeric>] Array of evenly spaced values
67
+ # @raise [ArgumentError] If num is less than 1
68
+ def linspace(start, stop, num = 50, endpoint = true)
69
+ raise ArgumentError, "Number of samples must be at least 1" if num < 1
70
+ return [start] if num == 1
71
+
72
+ step = if endpoint
73
+ (stop - start) / (num - 1).to_f
74
+ else
75
+ (stop - start) / num.to_f
65
76
  end
66
77
 
67
- # @return [Float] Interpolated value
68
- def interpolate_5_terms(terms, factor)
69
- y1, y2, y3, y4, y5 = terms
70
-
71
- a = y2 - y1
72
- b = y3 - y2
73
- c = y4 - y3
74
- d = y5 - y4
75
-
76
- e = b - a
77
- f = c - b
78
- g = d - c
78
+ result = Array.new(num)
79
+ current = start
79
80
 
80
- h = f - e
81
- j = g - f
82
-
83
- k = j - h
84
-
85
- y3 +
86
- factor * ((b + c) / 2.0 - (h + j) / 12.0) +
87
- factor**2 * (f / 2.0 - k / 24.0) +
88
- factor**3 * (h + j) / 12.0 +
89
- factor**4 * k / 24.0
81
+ (num - 1).times do |i|
82
+ result[i] = current
83
+ current += step
90
84
  end
85
+
86
+ result[num - 1] = endpoint ? stop : current
87
+ result
91
88
  end
92
89
  end
93
90
  end