package natal import ( "fmt" "math" "sort" ) // Aspect is a major aspect between two chart points. type Aspect struct { A string `json:"a"` B string `json:"b"` ATitle string `json:"a_title"` BTitle string `json:"b_title"` Type string `json:"type"` // conjunction|sextile|square|trine|opposition Angle float64 `json:"angle"` Orb float64 `json:"orb"` Label string `json:"label"` } var aspectDefs = []struct { Type string Angle float64 Orb float64 Label string }{ {"conjunction", 0, 8, "合相"}, {"sextile", 60, 6, "六合"}, {"square", 90, 7, "刑相"}, {"trine", 120, 7, "拱相"}, {"opposition", 180, 8, "对冲"}, } // majorKeys used for aspect table (外行星可选由前端过滤). var majorKeys = []string{"sun", "moon", "rise", "mercury", "venus", "mars", "jupiter", "saturn"} // Aspects computes major aspects among primary bodies. func Aspects(chart Chart) []Aspect { byKey := map[string]Body{} for _, p := range chart.Planets { byKey[p.Key] = p } var bodies []Body for _, k := range majorKeys { if b, ok := byKey[k]; ok { bodies = append(bodies, b) } } out := make([]Aspect, 0) for i := 0; i < len(bodies); i++ { for j := i + 1; j < len(bodies); j++ { a, b := bodies[i], bodies[j] diff := AngleDiff(a.Lon, b.Lon) for _, def := range aspectDefs { orb := math.Abs(diff - def.Angle) if orb <= def.Orb { out = append(out, Aspect{ A: a.Key, B: b.Key, ATitle: a.Title, BTitle: b.Title, Type: def.Type, Angle: def.Angle, Orb: round1(orb), Label: fmt.Sprintf("%s%s%s(容许%.1f°)", a.Title, def.Label, b.Title, orb), }) break } } } } sort.Slice(out, func(i, j int) bool { if out[i].Orb != out[j].Orb { return out[i].Orb < out[j].Orb } return out[i].Label < out[j].Label }) return out } // AspectsAsMaps for JSON embedding. func AspectsAsMaps(list []Aspect) []map[string]any { out := make([]map[string]any, 0, len(list)) for _, a := range list { out = append(out, map[string]any{ "a": a.A, "b": a.B, "a_title": a.ATitle, "b_title": a.BTitle, "type": a.Type, "angle": a.Angle, "orb": a.Orb, "label": a.Label, }) } return out } func round1(x float64) float64 { return math.Round(x*10) / 10 }