落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
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
|
|
}
|