Class: Astronoby::SolarSystemBody

Inherits:
Object
  • Object
show all
Extended by:
Body
Includes:
Position
Defined in:
lib/astronoby/bodies/solar_system_body.rb

Overview

Base class for solar system bodies. Provides the reference frame chain (geometric -> astrometric -> mean-of-date -> apparent -> topocentric) and common observational properties (phase angle, magnitude, etc.).

Direct Known Subclasses

Earth, Jupiter, Mars, Mercury, Moon, Neptune, Saturn, Sun, Uranus, Venus

Constant Summary collapse

SOLAR_SYSTEM_BARYCENTER =
0
SUN =
10
MERCURY_BARYCENTER =
1
MERCURY =
199
VENUS_BARYCENTER =
2
VENUS =
299
EARTH_MOON_BARYCENTER =
3
EARTH =
399
MOON =
301
MARS_BARYCENTER =
4
JUPITER_BARYCENTER =
5
SATURN_BARYCENTER =
6
URANUS_BARYCENTER =
7
NEPTUNE_BARYCENTER =
8

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Position

#observed_by

Constructor Details

#initialize(ephem:, instant:, orientation: nil) ⇒ SolarSystemBody

Returns a new instance of SolarSystemBody.

Parameters:

  • ephem (::Ephem::SPK)

    Ephemeris data source

  • instant (Astronoby::Instant)

    Instant for which to calculate the phase angle

  • orientation (Astronoby::Orientation, nil) (defaults to: nil)

    Orientation kernel, enabling arcsecond-accurate lunar libration and axis position angle



233
234
235
236
237
# File 'lib/astronoby/bodies/solar_system_body.rb', line 233

def initialize(ephem:, instant:, orientation: nil)
  @ephem = ephem
  @instant = instant
  @orientation = orientation
end

Instance Attribute Details

#ephem::Ephem::SPK (readonly)

Returns the ephemeris data source.

Returns:

  • (::Ephem::SPK)

    the ephemeris data source



30
31
32
# File 'lib/astronoby/bodies/solar_system_body.rb', line 30

def ephem
  @ephem
end

#instantAstronoby::Instant (readonly)

Returns the time instant.

Returns:



27
28
29
# File 'lib/astronoby/bodies/solar_system_body.rb', line 27

def instant
  @instant
end

#orientationAstronoby::Orientation? (readonly)

Returns the orientation kernel, if provided.

Returns:



33
34
35
# File 'lib/astronoby/bodies/solar_system_body.rb', line 33

def orientation
  @orientation
end

Class Method Details

.absolute_magnitudeFloat?

Returns absolute magnitude of the body.

Returns:

  • (Float, nil)

    absolute magnitude of the body



104
105
106
# File 'lib/astronoby/bodies/solar_system_body.rb', line 104

def self.absolute_magnitude
  nil
end

.at(instant, ephem:, orientation: nil) ⇒ Astronoby::SolarSystemBody

Creates a new body instance at the given instant.

Parameters:

Returns:



41
42
43
# File 'lib/astronoby/bodies/solar_system_body.rb', line 41

def self.at(instant, ephem:, orientation: nil)
  new(ephem: ephem, instant: instant, orientation: orientation)
end

.compute_geometric(ephem:, instant:) ⇒ Astronoby::Geometric

Returns the geometric frame.

Parameters:

  • ephem (::Ephem::SPK)

    ephemeris data source

  • instant (Astronoby::Instant)

    the time instant

Returns:



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/astronoby/bodies/solar_system_body.rb', line 57

def self.compute_geometric(ephem:, instant:)
  segments = ephemeris_segments(ephem.type)
  segment1 = segments[0]
  segment2 = segments[1] if segments.size == 2
  cache_key = CacheKey.generate(:geometric, instant, segment1, segment2)

  Astronoby.cache.fetch(cache_key) do
    state1 = ephem[*segment1].state_at(instant.tt)

    if segment2
      state2 = ephem[*segment2].state_at(instant.tt)
      position = state1.position + state2.position
      velocity = state1.velocity + state2.velocity
    else
      position = state1.position
      velocity = state1.velocity
    end

    position_vector = Vector[
      Distance.from_kilometers(position.x),
      Distance.from_kilometers(position.y),
      Distance.from_kilometers(position.z)
    ]

    velocity_vector = Vector[
      Velocity.from_kilometers_per_day(velocity.x),
      Velocity.from_kilometers_per_day(velocity.y),
      Velocity.from_kilometers_per_day(velocity.z)
    ]

    Geometric.new(
      position: position_vector,
      velocity: velocity_vector,
      instant: instant,
      target_body: self
    )
  end
end

.conjunction_events(ephem:, start_time:, end_time:, samples_per_period: 60) ⇒ Array<Astronoby::Conjunction>

Returns conjunctions with the Sun.

Parameters:

  • ephem (::Ephem::SPK)

    ephemeris data source

  • start_time (Time)

    start time

  • end_time (Time)

    end time

  • samples_per_period (Integer) (defaults to: 60)

    number of samples per synodic period

Returns:

Raises:



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/astronoby/bodies/solar_system_body.rb', line 162

def self.conjunction_events(
  ephem:,
  start_time:,
  end_time:,
  samples_per_period: 60
)
  unless planet?
    raise UnsupportedEventError, "#{self} has no conjunctions with the Sun"
  end

  ConjunctionOppositionCalculator.new(
    body: self,
    ephem: ephem,
    samples_per_period: samples_per_period
  ).conjunction_events_between(start_time, end_time)
end

.ephemeris_segments(_ephem_source) ⇒ Array<Array>

Returns ephemeris segment identifiers.

Parameters:

  • _ephem_source (Symbol)

    the ephemeris source type

Returns:

  • (Array<Array>)

    ephemeris segment identifiers

Raises:

  • (NotImplementedError)

    must be implemented by subclasses



99
100
101
# File 'lib/astronoby/bodies/solar_system_body.rb', line 99

def self.ephemeris_segments(_ephem_source)
  raise NotImplementedError
end

.geometric(ephem:, instant:) ⇒ Astronoby::Geometric

Computes the geometric reference frame for this body.

Parameters:

  • ephem (::Ephem::SPK)

    ephemeris data source

  • instant (Astronoby::Instant)

    the time instant

Returns:



50
51
52
# File 'lib/astronoby/bodies/solar_system_body.rb', line 50

def self.geometric(ephem:, instant:)
  compute_geometric(ephem: ephem, instant: instant)
end

.greatest_elongation_events(ephem:, start_time:, end_time:, samples_per_period: 60) ⇒ Array<Astronoby::GreatestElongation>

Returns greatest elongations.

Parameters:

  • ephem (::Ephem::SPK)

    ephemeris data source

  • start_time (Time)

    start time

  • end_time (Time)

    end time

  • samples_per_period (Integer) (defaults to: 60)

    number of samples per synodic period

Returns:

Raises:



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# File 'lib/astronoby/bodies/solar_system_body.rb', line 210

def self.greatest_elongation_events(
  ephem:,
  start_time:,
  end_time:,
  samples_per_period: 60
)
  unless inferior_planet?
    raise UnsupportedEventError,
      "#{self} has no greatest elongations from the Sun"
  end

  GreatestElongationCalculator.new(
    body: self,
    ephem: ephem,
    samples_per_period: samples_per_period
  ).greatest_elongation_events_between(start_time, end_time)
end

.inferior_planet?Boolean

Returns true for an inferior planet (Mercury, Venus).

Returns:

  • (Boolean)

    true for an inferior planet (Mercury, Venus)



109
110
111
# File 'lib/astronoby/bodies/solar_system_body.rb', line 109

def self.inferior_planet?
  false
end

.opposition_events(ephem:, start_time:, end_time:, samples_per_period: 60) ⇒ Array<Astronoby::Opposition>

Returns oppositions with the Sun.

Parameters:

  • ephem (::Ephem::SPK)

    ephemeris data source

  • start_time (Time)

    start time

  • end_time (Time)

    end time

  • samples_per_period (Integer) (defaults to: 60)

    number of samples per synodic period

Returns:

Raises:



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/astronoby/bodies/solar_system_body.rb', line 186

def self.opposition_events(
  ephem:,
  start_time:,
  end_time:,
  samples_per_period: 60
)
  unless superior_planet?
    raise UnsupportedEventError, "#{self} has no oppositions with the Sun"
  end

  ConjunctionOppositionCalculator.new(
    body: self,
    ephem: ephem,
    samples_per_period: samples_per_period
  ).opposition_events_between(start_time, end_time)
end

.planet?Boolean

Returns true for a planet (excludes the Sun, Earth and Moon).

Returns:

  • (Boolean)

    true for a planet (excludes the Sun, Earth and Moon)



119
120
121
# File 'lib/astronoby/bodies/solar_system_body.rb', line 119

def self.planet?
  inferior_planet? || superior_planet?
end

.rise_transit_set_events(observer:, ephem:, date: nil, start_time: nil, end_time: nil, utc_offset: 0) ⇒ Astronoby::RiseTransitSetEvent+

Returns Rise, transit, and set events for the given date or time range.

Parameters:

  • observer (Astronoby::Observer)

    Observer for whom to calculate rise, transit, and set events

  • ephem (::Ephem::SPK)

    Ephemeris data source

  • date (Date) (defaults to: nil)

    Date for which to calculate rise, transit, and set events (optional)

  • start_time (Time) (defaults to: nil)

    Start time for rise, transit, and set event calculation (optional)

  • end_time (Time) (defaults to: nil)

    End time for rise, transit, and set event calculation (optional)

  • utc_offset (String) (defaults to: 0)

    UTC offset for the given date (e.g., “+02:00”)

Returns:



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/astronoby/bodies/solar_system_body.rb', line 136

def self.rise_transit_set_events(
  observer:,
  ephem:,
  date: nil,
  start_time: nil,
  end_time: nil,
  utc_offset: 0
)
  calculator = RiseTransitSetCalculator.new(
    body: self,
    observer: observer,
    ephem: ephem
  )
  if date
    calculator.events_on(date, utc_offset: utc_offset)
  else
    calculator.events_between(start_time, end_time)
  end
end

.superior_planet?Boolean

Returns true for a superior planet (Mars through Neptune).

Returns:

  • (Boolean)

    true for a superior planet (Mars through Neptune)



114
115
116
# File 'lib/astronoby/bodies/solar_system_body.rb', line 114

def self.superior_planet?
  false
end

Instance Method Details

#angular_diameterAstronoby::Angle

Angular diameter of the body, as seen from Earth. Based on the apparent

position of the body.

Returns:



367
368
369
370
371
372
373
374
375
# File 'lib/astronoby/bodies/solar_system_body.rb', line 367

def angular_diameter
  @angular_radius ||= begin
    return if apparent.position.zero?

    Angle.from_radians(
      Math.asin(self.class::EQUATORIAL_RADIUS.m / apparent.distance.m) * 2
    )
  end
end

#apparentAstronoby::Apparent

Returns the apparent reference frame.

Returns:



274
275
276
277
278
279
280
281
# File 'lib/astronoby/bodies/solar_system_body.rb', line 274

def apparent
  @apparent ||= Apparent.build_from_astrometric(
    instant: @instant,
    target_astrometric: astrometric,
    earth_geometric: earth_geometric,
    target_body: body
  )
end

#apparent_magnitudeFloat?

Source:

Title: Astronomical Algorithms
Author: Jean Meeus
Edition: 2nd edition
Chapter: 48 - Illuminated Fraction of the Moon's Disk

Apparent magnitude of the body, as seen from Earth.

Returns:

  • (Float, nil)

    Apparent magnitude of the body.



352
353
354
355
356
357
358
359
360
361
362
# File 'lib/astronoby/bodies/solar_system_body.rb', line 352

def apparent_magnitude
  return unless self.class.absolute_magnitude

  @apparent_magnitude ||= begin
    body_sun_distance =
      (astrometric.position - sun.astrometric.position).magnitude
    self.class.absolute_magnitude +
      5 * Math.log10(body_sun_distance.au * astrometric.distance.au) +
      magnitude_correction_term
  end
end

#approaching_primary?Boolean

Returns True if the body is approaching its primary body, false otherwise.

Returns:

  • (Boolean)

    True if the body is approaching its primary body, false otherwise.



379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/astronoby/bodies/solar_system_body.rb', line 379

def approaching_primary?
  relative_position =
    (geometric.position - primary_body_geometric.position).map(&:m)
  relative_velocity =
    (geometric.velocity - primary_body_geometric.velocity).map(&:mps)
  radial_velocity_component = Astronoby::Util::Maths
    .dot_product(relative_position, relative_velocity)
  distance = Math.sqrt(
    Astronoby::Util::Maths.dot_product(relative_position, relative_position)
  )
  radial_velocity_component / distance < 0
end

#astrometricAstronoby::Astrometric

Returns the astrometric reference frame (GCRS).

Returns:



253
254
255
256
257
258
259
260
261
# File 'lib/astronoby/bodies/solar_system_body.rb', line 253

def astrometric
  @astrometric ||= Astrometric.build_from_geometric(
    instant: @instant,
    earth_geometric: earth_geometric,
    light_time_corrected_position: light_time_corrected_position,
    light_time_corrected_velocity: light_time_corrected_velocity,
    target_body: body
  )
end

#bodyAstronoby::Body

Returns the body definition (the class itself).

Returns:



284
285
286
# File 'lib/astronoby/bodies/solar_system_body.rb', line 284

def body
  self.class
end

#constellationAstronoby::Constellation?

Returns the constellation of the body

Returns:



290
291
292
293
294
295
296
297
# File 'lib/astronoby/bodies/solar_system_body.rb', line 290

def constellation
  @constellation ||= Constellations::Finder.find(
    Precession.for_equatorial_coordinates(
      coordinates: astrometric.equatorial,
      epoch: JulianDate::B1875
    )
  )
end

#earth_geometricAstronoby::Geometric

Returns Earth’s geometric reference frame.

Returns:



248
249
250
# File 'lib/astronoby/bodies/solar_system_body.rb', line 248

def earth_geometric
  @earth_geometric ||= Earth.geometric(ephem: @ephem, instant: @instant)
end

#eastern?Boolean

Returns true when the body is east of the Sun.

Returns:

  • (Boolean)

    true when the body is east of the Sun



308
309
310
311
# File 'lib/astronoby/bodies/solar_system_body.rb', line 308

def eastern?
  (apparent.ecliptic.longitude - sun.apparent.ecliptic.longitude)
    .sin.positive?
end

#elongationAstronoby::Angle?

Apparent geocentric Sun-Earth-body angle

Returns:



301
302
303
304
305
# File 'lib/astronoby/bodies/solar_system_body.rb', line 301

def elongation
  return unless sun

  @elongation ||= sun.apparent.separation_from(apparent)
end

#geometricAstronoby::Geometric

Returns the geometric reference frame (BCRS).

Returns:



240
241
242
243
244
245
# File 'lib/astronoby/bodies/solar_system_body.rb', line 240

def geometric
  @geometric ||= self.class.compute_geometric(
    ephem: @ephem,
    instant: @instant
  )
end

#illuminated_fractionFloat?

Fraction between 0 and 1 of the body’s disk that is illuminated.

Returns:

  • (Float, nil)

    Body’s illuminated fraction, between 0 and 1.



339
340
341
342
343
# File 'lib/astronoby/bodies/solar_system_body.rb', line 339

def illuminated_fraction
  return unless phase_angle

  @illuminated_fraction ||= (1 + phase_angle.cos) / 2.0
end

#mean_of_dateAstronoby::MeanOfDate

Returns the mean-of-date reference frame.

Returns:



264
265
266
267
268
269
270
271
# File 'lib/astronoby/bodies/solar_system_body.rb', line 264

def mean_of_date
  @mean_of_date ||= MeanOfDate.build_from_geometric(
    instant: @instant,
    target_geometric: geometric,
    earth_geometric: earth_geometric,
    target_body: body
  )
end

#phase_angleAstronoby::Angle?

Source:

Title: Astronomical Algorithms
Author: Jean Meeus
Edition: 2nd edition
Chapter: 48 - Illuminated Fraction of the Moon's Disk

Returns:



324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/astronoby/bodies/solar_system_body.rb', line 324

def phase_angle
  return unless sun

  @phase_angle ||= begin
    term1 = sun.astrometric.distance.km * elongation.sin
    term2 = astrometric.distance.km -
      sun.astrometric.distance.km * elongation.cos
    angle = Angle.atan(term1 / term2)
    Astronoby::Util::Trigonometry
      .adjustement_for_arctangent(term1, term2, angle)
  end
end

#receding_from_primary?Boolean

Returns True if the body is receding from its primary body, false otherwise.

Returns:

  • (Boolean)

    True if the body is receding from its primary body, false otherwise.



394
395
396
# File 'lib/astronoby/bodies/solar_system_body.rb', line 394

def receding_from_primary?
  !approaching_primary?
end

#western?Boolean

Returns true when the body is west of the Sun.

Returns:

  • (Boolean)

    true when the body is west of the Sun



314
315
316
# File 'lib/astronoby/bodies/solar_system_body.rb', line 314

def western?
  !eastern?
end