Module: AstroChart::Transits

Defined in:
lib/astro_chart/transits.rb

Overview

Transits (行運): where the planets are in the sky right now (or at any moment), compared against a natal chart.

Pure composition of existing modules — Planets.calculate_positions gives the sky positions for any JD, Houses.find_house places them into the natal houses, and Aspects.calculate scores transit-to-natal contacts.

Typical usage with a Chart#generate result:

jd = AstroChart::TimeConversion.to_julian_day("2026-07-24", "12:00", "Asia/Taipei")
AstroChart::Transits.at(jd)                  # 天象快照 { "太陽" => 121.9, ... }
AstroChart::Transits.against(natal_chart, jd) # 行運行星落入本命宮位 + 行運相位

Constant Summary collapse

ASPECT_BODIES =

Bodies used for transit-to-natal aspects. Mirrors Synastry: 南交點 is excluded because it sits exactly opposite 北交點 — every south-node aspect would just mirror a north-node one and double the noise.

Synastry::BODIES

Class Method Summary collapse

Class Method Details

.against(natal_chart, jd, orb_limit: 3.0) ⇒ Object

Transits against a natal chart (a Chart#generate result hash).

jd: Julian Day of the transit moment. orb_limit: keep only aspects with orb <= limit (default 3.0 — transit practice uses much tighter orbs than the natal defaults in Aspects).

Returns:

{
"planets" => [ { "planet" => "木星", "zodiac" => "獅子座",
                 "degree" => 5.1, "total_degree" => 125.1,
                 "natal_house" => 7 }, ... 12 entries ],
"aspects" => [ { "transit_planet" => "土星", "natal_planet" => "太陽",
                 "aspect_type" => "四分相", "orb" => 1.23 }, ... ]
}

aspects are sorted by orb (tightest first).



53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/astro_chart/transits.rb', line 53

def self.against(natal_chart, jd, orb_limit: 3.0)
  transit_positions = at(jd)
  natal_positions   = Synastry.positions_from_chart(natal_chart)
  natal_cusps       = Synastry.cusps_from_chart(natal_chart)

  {
    "planets" => planet_details(transit_positions, natal_cusps),
    "aspects" => aspects_to_natal(transit_positions, natal_positions,
                                  orb_limit: orb_limit,
                                  keys: %w[transit_planet natal_planet]),
  }
end

.aspects_to_natal(moving_positions, natal_positions, orb_limit:, keys: %w[transit_planet natal_planet])) ⇒ Object

Aspects from moving positions to natal positions, filtered by orb_limit and sorted by orb. keys names the two hash keys so Progressions can reuse this with "progressed_planet".



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/astro_chart/transits.rb', line 83

def self.aspects_to_natal(moving_positions, natal_positions, orb_limit:,
                          keys: %w[transit_planet natal_planet])
  moving_key, natal_key = keys
  results = []

  moving_positions.each do |m_name, m_pos|
    next unless ASPECT_BODIES.include?(m_name)

    natal_positions.each do |n_name, n_pos|
      aspect_type, orb = Aspects.calculate(m_pos, n_pos)
      next if aspect_type.nil?
      next if orb_limit && orb > orb_limit

      results << {
        moving_key    => m_name,
        natal_key     => n_name,
        "aspect_type" => aspect_type,
        "orb"         => orb,
      }
    end
  end

  results.sort_by { |r| r["orb"] }
end

.at(jd) ⇒ Object

Sky snapshot at a Julian Day.

Returns { "太陽" => 123.45, ..., "南交點" => 303.45 } — ecliptic longitudes (total degrees 0-360) for the 12 bodies (Ephemeris::PLANETS + 南交點 = 北交點 + 180°).



33
34
35
# File 'lib/astro_chart/transits.rb', line 33

def self.at(jd)
  Planets.calculate_positions(jd)
end

.planet_details(positions, natal_cusps) ⇒ Object

Build the per-planet detail list, placing each transiting body into the natal houses.



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/astro_chart/transits.rb', line 68

def self.planet_details(positions, natal_cusps)
  positions.map do |name, pos|
    {
      "planet"       => name,
      "zodiac"       => Zodiac.sign_name(pos),
      "degree"       => (pos % 30).round(4),
      "total_degree" => pos.round(4),
      "natal_house"  => Houses.find_house(pos, natal_cusps),
    }
  end
end