Class: AstroChart::Chart

Inherits:
Object
  • Object
show all
Defined in:
lib/astro_chart/chart.rb

Constant Summary collapse

HOUSE_SYSTEMS =

Supported house systems: "P" (Placidus) / "W" (Whole Sign) / "E" (Equal, from the ascendant) / "O" (Porphyry).

["P", "W", "E", "O"].freeze
NEVER_RETROGRADE =

Bodies that can never be retrograde in geocentric longitude.

["太陽", "月亮"].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(birth_date:, birth_time:, latitude:, longitude:, timezone:, house_system: "P") ⇒ Chart

birth_date: "YYYY-MM-DD" birth_time: "HH:MM" latitude / longitude: Float timezone: IANA timezone string (e.g. "Asia/Taipei") house_system: "P" (Placidus, default), "W" (Whole Sign), "E" (Equal, from the ascendant) or "O" (Porphyry)



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/astro_chart/chart.rb', line 19

def initialize(birth_date:, birth_time:, latitude:, longitude:, timezone:,
               house_system: "P")
  unless HOUSE_SYSTEMS.include?(house_system)
    raise ArgumentError,
          "unknown house system #{house_system.inspect} " \
          "(expected \"P\", \"W\", \"E\" or \"O\")"
  end

  @birth_date   = birth_date
  @birth_time   = birth_time
  @latitude     = latitude.to_f
  @longitude    = longitude.to_f
  @timezone     = timezone
  @house_system = house_system
end

Instance Attribute Details

#birth_dateObject (readonly)

Returns the value of attribute birth_date.



10
11
12
# File 'lib/astro_chart/chart.rb', line 10

def birth_date
  @birth_date
end

#birth_timeObject (readonly)

Returns the value of attribute birth_time.



10
11
12
# File 'lib/astro_chart/chart.rb', line 10

def birth_time
  @birth_time
end

#house_systemObject (readonly)

Returns the value of attribute house_system.



10
11
12
# File 'lib/astro_chart/chart.rb', line 10

def house_system
  @house_system
end

#latitudeObject (readonly)

Returns the value of attribute latitude.



10
11
12
# File 'lib/astro_chart/chart.rb', line 10

def latitude
  @latitude
end

#longitudeObject (readonly)

Returns the value of attribute longitude.



10
11
12
# File 'lib/astro_chart/chart.rb', line 10

def longitude
  @longitude
end

#timezoneObject (readonly)

Returns the value of attribute timezone.



10
11
12
# File 'lib/astro_chart/chart.rb', line 10

def timezone
  @timezone
end

Instance Method Details

#generateObject

Generate complete chart data. Returns a Hash with string keys, compatible with the existing JSON API.



37
38
39
40
41
42
43
44
45
46
47
48
49
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/astro_chart/chart.rb', line 37

def generate
  jd = TimeConversion.to_julian_day(birth_date, birth_time, timezone)

  cusps, ascendant = Houses.calculate(jd, latitude, longitude, system: house_system)
  asc_zodiac = Zodiac.sign_name(ascendant)

  positions = Planets.calculate_positions(jd)
  planet_details = Planets.build_details(positions, cusps)

  # Retrograde flags for the 12 physical bodies (太陽/月亮 hard-false,
  # 南交點 mirrors 北交點 — they share the same axis).
  retro = retrograde_flags(jd)
  planet_details.each { |p| p["retrograde"] = retro.fetch(p["planet"], false) }

  kp = Planets.key_points_data(positions, cusps, ascendant)

  # Merge aspects into planet details
  aspect_map = {
    "太陽"  => "sun_aspects",
    "月亮"  => "moon_aspects",
    "土星"  => "saturn_aspects",
    "金星"  => "venus_aspects",
    "北交點" => "north_node_aspects",
    "南交點" => "south_node_aspects",
  }

  planet_details.each do |planet|
    key = aspect_map[planet["planet"]]
    planet["aspects"] = kp[key] if key
  end

  # Derived points (福點 / 莉莉絲), placed before the ruler points
  planet_details.concat(derived_points(jd, positions, cusps, ascendant))

  # Append ruler points (定位星 carry no retrograde motion of their own)
  ruler_points = kp["additional_points"].map { |p| p.merge("retrograde" => false) }
  planet_details.concat(ruler_points)

  houses_data = cusps.each_with_index.map do |deg, i|
    {
      "house_number" => i + 1,
      "degree"       => deg.round(4),
      "zodiac"       => Zodiac.sign_name(deg),
    }
  end

  # 12-body positions feed pattern detection; the 10 classical planets
  # (nodes excluded) feed the element/modality statistics.
  classical = positions.reject { |name, _| name == "北交點" || name == "南交點" }

  {
    "input" => {
      "birth_date"  => birth_date,
      "birth_time"  => birth_time,
      "coordinates" => {
        "latitude"  => latitude,
        "longitude" => longitude,
      },
      "timezone"    => timezone,
    },
    "chart" => {
      "ascendant" => {
        "zodiac"       => asc_zodiac,
        "degree"       => (ascendant % 30).round(4),
        "total_degree" => ascendant.round(4),
      },
      "house_system"  => house_system,
      "planets"       => planet_details,
      "houses"        => houses_data,
      "patterns"      => Patterns.detect(positions),
      "element_stats" => Stats.elements(classical),
    },
  }
end