Module: AstroChart::Composite

Defined in:
lib/astro_chart/composite.rb

Overview

Composite chart (組合盤): the midpoint chart of two natal charts.

For every body present in BOTH charts, the composite position is the shorter-arc midpoint of the two natal longitudes. Aspects are then computed among the composite positions themselves.

Backend-independent: works purely on Chart#generate result hashes, no ephemeris call happens here.

result = Composite.between(chart_a, chart_b)
result["planets"] # 12 個組合盤中點位置
result["aspects"] # 組合盤位置彼此之間的相位(依 orb 排序)

Houses are intentionally out of scope: midpoint-composite house systems are convention-contested (midpoint of cusps vs. recomputed for a reference location/time), so no single answer is returned here.

Constant Summary collapse

BODIES =

The 12 bodies considered: the 11 ephemeris bodies + 南交點.

(Ephemeris::PLANETS.keys + ["南交點"]).freeze

Class Method Summary collapse

Class Method Details

.between(chart_a, chart_b) ⇒ Object

Full composite between two Chart#generate result hashes.

Returns:

{
"planets" => [{ "planet", "zodiac", "degree", "total_degree" }],
"aspects" => [{ "planet_a", "planet_b", "aspect_type", "orb" }],
}


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/astro_chart/composite.rb', line 29

def self.between(chart_a, chart_b)
  pos_a = positions_from_chart(chart_a)
  pos_b = positions_from_chart(chart_b)

  composite_positions = {}
  BODIES.each do |name|
    a = pos_a[name]
    b = pos_b[name]
    next if a.nil? || b.nil?

    composite_positions[name] = midpoint(a, b)
  end

  {
    "planets" => build_planets(composite_positions),
    "aspects" => build_aspects(composite_positions),
  }
end

.midpoint(a, b) ⇒ Object

Shorter-arc midpoint of two ecliptic longitudes (degrees, [0, 360)). 350° & 10° => 0° (not 180°).



50
51
52
# File 'lib/astro_chart/composite.rb', line 50

def self.midpoint(a, b)
  (a + (((b - a + 540.0) % 360.0) - 180.0) / 2.0) % 360.0
end

.positions_from_chart(chart) ⇒ Object

Extract { name => total_degree } for BODIES from a Chart#generate hash. Ruler points appended by key_points_data are ignored (not in BODIES).

Raises:

  • (ArgumentError)


56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/astro_chart/composite.rb', line 56

def self.positions_from_chart(chart)
  planets = chart&.dig("chart", "planets")
  raise ArgumentError, "chart has no planets data" if planets.nil? || planets.empty?

  planets.each_with_object({}) do |p, out|
    name = p["planet"]
    next unless BODIES.include?(name)

    degree = p["total_degree"]
    out[name] = degree if degree
  end
end