Module: AstroChart::TransitTiming
- Defined in:
- lib/astro_chart/transit_timing.rb
Overview
Transit timing (行運精確時點): the exact UTC instants, within a date range, when a transiting body forms an exact aspect to a natal point.
Where Transits.against is a single snapshot ("what aspects hold right now?"), this answers the question people actually ask — "when does transiting 土星 exactly square my natal 太陽?".
natal = AstroChart::Chart.new(...).generate
events = AstroChart::TransitTiming.events(natal, "2026-01-01", "2026-12-31")
events.first
#=> { "transit_planet" => "土星", "natal_planet" => "太陽",
# "aspect_type" => "四分相", "jd" => 2461...,
# "time_utc" => "2026-03-14T07:22:10Z", "transit_zodiac" => "牡羊座",
# "retrograde" => false }
Method: each transiting body's longitude is sampled across the range at
step_days; for every natal point and aspect angle the signed separation
is bracketed where it crosses exactness, then refined by bisection (robust
through retrograde stations, unlike a pure Newton step). A step where the
body moves < 90° guarantees no aliasing — 1 day is safe for all bodies
(the Moon moves ~13°/day).
Constant Summary collapse
- BODIES =
Transiting bodies considered (12-body set minus 南交點, mirroring Synastry/Transits — a south-node hit merely mirrors a north-node one).
Synastry::BODIES
- MAJOR =
Major aspect angles. Each non-zero, non-opposition aspect is exact at two signed separations (applying from either side); 合相/對分相 have one.
{ "合相" => 0, "六分相" => 60, "四分相" => 90, "三分相" => 120, "對分相" => 180 }.freeze
- MINOR =
{ "十二分相" => 30, "半四分相" => 45, "補八分相" => 135, "補十二分相" => 150 }.freeze
- CONVERGENCE_DAYS =
~0.1 second of clock time
1e-6- MAX_BISECT =
60
Class Method Summary collapse
-
.events(natal_chart, start_date, end_date, minor: false, step_days: 1.0, bodies: nil) ⇒ Object
natal_chart: a Chart#generate result hash.
Class Method Details
.events(natal_chart, start_date, end_date, minor: false, step_days: 1.0, bodies: nil) ⇒ Object
natal_chart: a Chart#generate result hash. start_date / end_date: "YYYY-MM-DD" (interpreted at 00:00 UT). minor: also time the minor aspects (30/45/135/150). step_days: sampling stride (default 1.0 — safe for every body). bodies: restrict transiting bodies (default all BODIES; e.g. drop "月亮" to avoid the Moon's ~monthly hits flooding the list).
Returns an Array of event hashes sorted by time (jd ascending).
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/astro_chart/transit_timing.rb', line 50 def self.events(natal_chart, start_date, end_date, minor: false, step_days: 1.0, bodies: nil) natal = Synastry.positions_from_chart(natal_chart) moving = (bodies || BODIES).select { |b| Ephemeris::PLANETS.key?(b) } targets = signed_targets(minor) jd_start = date_to_jd(start_date) jd_end = date_to_jd(end_date) events = [] moving.each do |body| id = Ephemeris::PLANETS[body] # Sample this body's longitude ONCE across the grid, then reuse the # cached samples for every natal point × aspect target (calc_ut is the # cost; the scan phase does zero extra ephemeris calls, only bisection # refinement recomputes). grid = sample_grid(id, jd_start, jd_end, step_days) natal.each do |n_name, n_pos| targets.each do |aspect_type, sep| scan_grid(grid, n_pos, sep).each do |jd0, h0, jd1, h1| jd = bisect(id, n_pos, sep, jd0, h0, jd1, h1) events << build_event(body, id, n_name, aspect_type, jd) end end end end dedupe(events).sort_by { |e| e["jd"] } end |