astronoby 0.6.0 → 0.7.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +116 -0
  5. data/Gemfile.lock +45 -23
  6. data/README.md +42 -285
  7. data/UPGRADING.md +238 -0
  8. data/lib/astronoby/aberration.rb +56 -31
  9. data/lib/astronoby/angle.rb +20 -16
  10. data/lib/astronoby/angles/dms.rb +2 -2
  11. data/lib/astronoby/angles/hms.rb +2 -2
  12. data/lib/astronoby/bodies/earth.rb +56 -0
  13. data/lib/astronoby/bodies/jupiter.rb +11 -0
  14. data/lib/astronoby/bodies/mars.rb +11 -0
  15. data/lib/astronoby/bodies/mercury.rb +11 -0
  16. data/lib/astronoby/bodies/moon.rb +50 -290
  17. data/lib/astronoby/bodies/neptune.rb +11 -0
  18. data/lib/astronoby/bodies/saturn.rb +11 -0
  19. data/lib/astronoby/bodies/solar_system_body.rb +122 -0
  20. data/lib/astronoby/bodies/sun.rb +16 -220
  21. data/lib/astronoby/bodies/uranus.rb +11 -0
  22. data/lib/astronoby/bodies/venus.rb +11 -0
  23. data/lib/astronoby/constants.rb +13 -1
  24. data/lib/astronoby/coordinates/ecliptic.rb +2 -37
  25. data/lib/astronoby/coordinates/equatorial.rb +25 -7
  26. data/lib/astronoby/coordinates/horizontal.rb +0 -46
  27. data/lib/astronoby/corrections/light_time_delay.rb +90 -0
  28. data/lib/astronoby/deflection.rb +187 -0
  29. data/lib/astronoby/distance.rb +9 -0
  30. data/lib/astronoby/ephem.rb +39 -0
  31. data/lib/astronoby/equinox_solstice.rb +21 -18
  32. data/lib/astronoby/errors.rb +4 -0
  33. data/lib/astronoby/events/moon_phases.rb +2 -1
  34. data/lib/astronoby/events/rise_transit_set_calculator.rb +352 -0
  35. data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
  36. data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
  37. data/lib/astronoby/events/twilight_calculator.rb +166 -0
  38. data/lib/astronoby/events/twilight_event.rb +28 -0
  39. data/lib/astronoby/instant.rb +171 -0
  40. data/lib/astronoby/mean_obliquity.rb +23 -10
  41. data/lib/astronoby/nutation.rb +227 -42
  42. data/lib/astronoby/observer.rb +55 -0
  43. data/lib/astronoby/precession.rb +91 -17
  44. data/lib/astronoby/reference_frame.rb +49 -0
  45. data/lib/astronoby/reference_frames/apparent.rb +60 -0
  46. data/lib/astronoby/reference_frames/astrometric.rb +21 -0
  47. data/lib/astronoby/reference_frames/geometric.rb +20 -0
  48. data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
  49. data/lib/astronoby/reference_frames/topocentric.rb +82 -0
  50. data/lib/astronoby/true_obliquity.rb +2 -1
  51. data/lib/astronoby/util/maths.rb +70 -73
  52. data/lib/astronoby/util/time.rb +454 -31
  53. data/lib/astronoby/vector.rb +36 -0
  54. data/lib/astronoby/velocity.rb +116 -0
  55. data/lib/astronoby/version.rb +1 -1
  56. data/lib/astronoby.rb +26 -5
  57. metadata +61 -16
  58. data/.tool-versions +0 -1
  59. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
  60. data/lib/astronoby/events/observation_events.rb +0 -285
  61. data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -218
  62. data/lib/astronoby/events/twilight_events.rb +0 -121
  63. data/lib/astronoby/util/astrodynamics.rb +0 -60
@@ -0,0 +1,82 @@
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 angular_diameter
52
+ @angular_radius ||= begin
53
+ return Angle.zero if @position.zero?
54
+
55
+ Angle.from_radians(
56
+ Math.atan(@target_body.class::EQUATORIAL_RADIUS.m / distance.m) * 2
57
+ )
58
+ end
59
+ end
60
+
61
+ def ecliptic
62
+ @ecliptic ||= begin
63
+ return Coordinates::Ecliptic.zero if distance.zero?
64
+
65
+ equatorial.to_ecliptic(epoch: @instant.tdb)
66
+ end
67
+ end
68
+
69
+ def horizontal(refraction: false)
70
+ horizontal = equatorial.to_horizontal(
71
+ time: @instant.to_time,
72
+ observer: @observer
73
+ )
74
+
75
+ if refraction
76
+ Refraction.correct_horizontal_coordinates(coordinates: horizontal)
77
+ else
78
+ horizontal
79
+ end
80
+ end
81
+ end
82
+ end
@@ -3,8 +3,9 @@
3
3
  module Astronoby
4
4
  class TrueObliquity
5
5
  def self.for_epoch(epoch)
6
+ instant = Instant.from_utc_julian_date(epoch)
6
7
  mean_obliquity = MeanObliquity.for_epoch(epoch)
7
- nutation = Nutation.for_obliquity_of_the_ecliptic(epoch: epoch)
8
+ nutation = Nutation.new(instant: instant).nutation_in_obliquity
8
9
 
9
10
  mean_obliquity + nutation
10
11
  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