Skip to content

Commit

Permalink
Add Geometric and Astrometric positions for Sun and Moon (#135)
Browse files Browse the repository at this point in the history
This is the start of the transition for `Sun` and `Moon` from formula-based to ephemeris-based.

It gives the ability to `Sun` and `Moon` to be initialized from a given `Instant` and `Ephem`.

`Planet` is renamed into `SolarSystemBody` so that `Sun` and `Moon` can inherit from it while still making sense.

Thanks to `SolarSystemBody`, `Sun` and `Moon` have Geometric and Astrometric positions, with each equatorial and ecliptic coordinates.

```rb
instant = Astronoby::Instant.from_time(Time.utc(2025, 2, 7))
ephem = Astronoby::Ephem.load("de440s.bsp")


sun = Astronoby::Sun.new(instant: instant, ephem: ephem)

sun.geometric.equatorial.right_ascension
sun.geometric.equatorial.declination
sun.geometric.ecliptic.latitude
sun.geometric.ecliptic.longitude
sun.geometric.distance
sun.astrometric.equatorial.right_ascension
sun.astrometric.equatorial.declination
sun.astrometric.ecliptic.latitude
sun.astrometric.ecliptic.longitude
sun.astrometric.distance


moon = Astronoby::Moon.new(instant: instant, ephem: ephem)

moon.geometric.equatorial.right_ascension
moon.geometric.equatorial.declination
moon.geometric.ecliptic.latitude
moon.geometric.ecliptic.longitude
moon.geometric.distance
moon.astrometric.equatorial.right_ascension
moon.astrometric.equatorial.declination
moon.astrometric.ecliptic.latitude
moon.astrometric.ecliptic.longitude
moon.astrometric.distance
```
  • Loading branch information
rhannequin authored Feb 21, 2025
1 parent 10ee22c commit a481ca2
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 26 deletions.
2 changes: 1 addition & 1 deletion lib/astronoby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
require "astronoby/velocity"
require "astronoby/astronomical_models/ephemeride_lunaire_parisienne"
require "astronoby/astronomical_models/moon_phases_periodic_terms"
require "astronoby/bodies/solar_system_body"
require "astronoby/bodies/moon"
require "astronoby/bodies/sun"
require "astronoby/bodies/planet"
require "astronoby/bodies/mercury"
require "astronoby/bodies/venus"
require "astronoby/bodies/earth"
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/earth.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Earth < Planet
class Earth < SolarSystemBody
def self.ephemeris_segments
[
[SOLAR_SYSTEM_BARYCENTER, EARTH_MOON_BARYCENTER],
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/jupiter.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Jupiter < Planet
class Jupiter < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, JUPITER_BARYCENTER]]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/mars.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Mars < Planet
class Mars < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, MARS_BARYCENTER]]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/mercury.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Mercury < Planet
class Mercury < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, MERCURY_BARYCENTER]]
end
Expand Down
16 changes: 14 additions & 2 deletions lib/astronoby/bodies/moon.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
# frozen_string_literal: true

module Astronoby
class Moon
class Moon < SolarSystemBody
SEMIDIAMETER_VARIATION = 0.7275
MEAN_GEOCENTRIC_DISTANCE = Astronoby::Distance.from_meters(385_000_560)

def self.ephemeris_segments
[
[SOLAR_SYSTEM_BARYCENTER, EARTH_MOON_BARYCENTER],
[EARTH_MOON_BARYCENTER, MOON]
]
end

# Source:
# Title: Astronomical Algorithms
# Author: Jean Meeus
Expand All @@ -18,8 +25,13 @@ def self.monthly_phase_events(year:, month:)
Events::MoonPhases.phases_for(year: year, month: month)
end

def initialize(time:)
def initialize(time: nil, instant: nil, ephem: nil)
@time = time
@instant = instant
@ephem = ephem
unless @instant.nil? || @ephem.nil?
super(instant: instant, ephem: ephem)
end
end

# @return [Astronoby::Coordinates::Ecliptic] Apparent ecliptic coordinates
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/neptune.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Neptune < Planet
class Neptune < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, NEPTUNE_BARYCENTER]]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/saturn.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Saturn < Planet
class Saturn < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, SATURN_BARYCENTER]]
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
# frozen_string_literal: true

module Astronoby
class Planet
class SolarSystemBody
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
Expand Down
13 changes: 11 additions & 2 deletions lib/astronoby/bodies/sun.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Sun
class Sun < SolarSystemBody
SEMI_MAJOR_AXIS_IN_METERS = 149_598_500_000
ANGULAR_DIAMETER = Angle.from_degrees(0.533128)
INTERPOLATION_FACTOR = 24.07
Expand All @@ -25,6 +25,10 @@ class Sun

attr_reader :time

def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, SUN]]
end

# Source:
# Title: Astronomical Algorithms
# Author: Jean Meeus
Expand Down Expand Up @@ -70,8 +74,13 @@ def self.equation_of_time(date_or_time:)
# Chapter: 6 - The Sun

# @param time [Time] Considered time
def initialize(time:)
def initialize(time: nil, instant: nil, ephem: nil)
@time = time
@instant = instant
@ephem = ephem
unless @instant.nil? || @ephem.nil?
super(instant: instant, ephem: ephem)
end
end

def epoch
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/uranus.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Uranus < Planet
class Uranus < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, URANUS_BARYCENTER]]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/bodies/venus.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true

module Astronoby
class Venus < Planet
class Venus < SolarSystemBody
def self.ephemeris_segments
[[SOLAR_SYSTEM_BARYCENTER, VENUS_BARYCENTER]]
end
Expand Down
18 changes: 12 additions & 6 deletions lib/astronoby/reference_frame.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,27 @@ def initialize(
end

def equatorial
return Coordinates::Equatorial.zero if distance.zero?
@equatorial ||= begin
return Coordinates::Equatorial.zero if distance.zero?

Coordinates::Equatorial.from_position_vector(@position)
Coordinates::Equatorial.from_position_vector(@position)
end
end

def ecliptic
return Coordinates::Ecliptic.zero if distance.zero?
@ecliptic ||= begin
return Coordinates::Ecliptic.zero if distance.zero?

equatorial.to_ecliptic(epoch: Epoch::J2000)
equatorial.to_ecliptic(epoch: Epoch::J2000)
end
end

def distance
return Distance.zero if @position.zero?
@distance ||= begin
return Distance.zero if @position.zero?

Astronoby::Distance.from_meters(@position.magnitude)
Astronoby::Distance.from_meters(@position.magnitude)
end
end
end
end
2 changes: 1 addition & 1 deletion lib/astronoby/reference_frames/astrometric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def self.build_from_geometric(
position: corrected_position - earth_geometric.position,
velocity: corrected_velocity - earth_geometric.velocity,
instant: instant,
center_identifier: Planet::EARTH,
center_identifier: SolarSystemBody::EARTH,
target_body: target_body
)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/astronoby/reference_frames/geometric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def initialize(
position: position,
velocity: velocity,
instant: instant,
center_identifier: Planet::SOLAR_SYSTEM_BARYCENTER,
center_identifier: SolarSystemBody::SOLAR_SYSTEM_BARYCENTER,
target_body: target_body
)
end
Expand Down
157 changes: 157 additions & 0 deletions spec/astronoby/bodies/moon_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,163 @@
# frozen_string_literal: true

RSpec.describe Astronoby::Moon do
include TestEphemHelper

describe "#geometric" do
it "returns a Geometric position" do
time = Time.utc(2025, 2, 7, 12)
instant = Astronoby::Instant.from_time(time)
state = double(
position: Ephem::Core::Vector[1, 2, 3],
velocity: Ephem::Core::Vector[4, 5, 6]
)
segment = double(compute_and_differentiate: state)
ephem = double(:[] => segment)
body = described_class.new(instant: instant, ephem: ephem)

geometric = body.geometric

expect(geometric).to be_a(Astronoby::Geometric)
expect(geometric.position)
.to eq(
Astronoby::Vector[
Astronoby::Distance.from_kilometers(2),
Astronoby::Distance.from_kilometers(4),
Astronoby::Distance.from_kilometers(6)
]
)
expect(geometric.velocity)
.to eq(
Astronoby::Vector[
Astronoby::Velocity.from_kilometers_per_day(8),
Astronoby::Velocity.from_kilometers_per_day(10),
Astronoby::Velocity.from_kilometers_per_day(12)
]
)
end

it "computes the correct position" do
time = Time.utc(2025, 3, 1)
instant = Astronoby::Instant.from_time(time)
ephem = test_ephem
body = described_class.new(instant: instant, ephem: ephem)

geometric = body.geometric

expect(geometric.position.to_a.map(&:km).map(&:round))
.to eq([-139986171, 45092089, 19573501])
# IMCCE: -139986152 45092088 19573500
# Skyfield: -139986173 45092085 19573500

expect(geometric.equatorial.right_ascension.str(:hms))
.to eq("10h 48m 34.8736s")
# IMCCE: 10h 48m 34.8733s
# Skyfield: 10h 48m 34.87s

expect(geometric.equatorial.declination.str(:dms))
.to eq("+7° 34′ 51.4383″")
# IMCCE: +7° 34′ 51.439″
# Skyfield: +7° 34′ 51.4″

expect(geometric.ecliptic.latitude.str(:dms))
.to eq("+0° 0′ 30.2285″")
# IMCCE: +0° 0′ 30.227″
# Skyfield: +0° 0′ 33.1″

expect(geometric.ecliptic.longitude.str(:dms))
.to eq("+160° 39′ 3.37″")
# IMCCE: +160° 39′ 3.365″
# Skyfield: +161° 0′ 10.3″

expect(geometric.distance.au)
.to eq(0.9917671780230503)
# IMCCE: 0.991767054173
# Skyfield: 0.9917671790668138
end

it "computes the correct velocity" do
time = Time.utc(2025, 3, 1)
instant = Astronoby::Instant.from_time(time)
ephem = test_ephem
body = described_class.new(instant: instant, ephem: ephem)

geometric = body.geometric

expect(geometric.velocity.to_a.map(&:mps).map { _1.round(5) })
.to eq([-10408.41307, -24901.72774, -10687.38588])
# IMCCE: -10408.41236 -24901.72816 -10687.38603
# Skyfield: -10408.41257 -24901.72803 -10687.38600
end
end

describe "#astrometric" do
it "returns an Astrometric position" do
time = Time.utc(2025, 2, 7, 12)
instant = Astronoby::Instant.from_time(time)
state = double(
position: Ephem::Core::Vector[1, 2, 3],
velocity: Ephem::Core::Vector[4, 5, 6]
)
segment = double(compute_and_differentiate: state)
ephem = double(:[] => segment)
body = described_class.new(instant: instant, ephem: ephem)

astrometric = body.astrometric

expect(astrometric).to be_a(Astronoby::Astrometric)
expect(astrometric.equatorial).to be_a(Astronoby::Coordinates::Equatorial)
expect(astrometric.distance).to be_a(Astronoby::Distance)
end

it "computes the correct position" do
time = Time.utc(2025, 3, 1)
instant = Astronoby::Instant.from_time(time)
ephem = test_ephem
body = described_class.new(instant: instant, ephem: ephem)

astrometric = body.astrometric

expect(astrometric.equatorial.right_ascension.str(:hms))
.to eq("23h 36m 54.8281s")
# IMCCE: 23h 36m 54.8384s
# Skyfield: 23h 36m 54.83s

expect(astrometric.equatorial.declination.str(:dms))
.to eq("-2° 50′ 44.532″")
# IMCCE: -2° 50′ 44.459″
# Skyfield: -2° 50′ 44.5″

expect(astrometric.ecliptic.latitude.str(:dms))
.to eq("-0° 19′ 14.6203″")
# IMCCE: -0° 19′ 14.614″
# Skyfield: -0° 19′ 14.9″

expect(astrometric.ecliptic.longitude.str(:dms))
.to eq("+353° 34′ 30.4661″")
# IMCCE: +353° 34′ 30.636″
# Skyfield: +353° 55′ 37.5″

expect(astrometric.distance.au)
.to eq(0.002423811076386272)
# IMCCE: 0.002423811046
# Skyfield: 0.002423811056514585
end

it "computes the correct velocity" do
time = Time.utc(2025, 3, 1)
instant = Astronoby::Instant.from_time(time)
ephem = test_ephem
body = described_class.new(instant: instant, ephem: ephem)

astrometric = body.astrometric

expect(astrometric.velocity.to_a.map(&:mps).map { _1.round(5) })
.to eq([105.49678, 948.38896, 519.75459])
# IMCCE: 105.49594 948.38904 519.75463
# Skyfield: 105.49622 948.38902 519.75462
end
end

describe "::monthly_phase_events" do
it "returns an array" do
moon_phases = described_class.monthly_phase_events(year: 2024, month: 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

RSpec.describe Astronoby::Planet do
RSpec.describe Astronoby::SolarSystemBody do
describe "::geometric" do
it "returns a Geometric position" do
time = Time.utc(2025, 2, 7, 12)
Expand Down
Loading

0 comments on commit a481ca2

Please sign in to comment.