feat: P1 合盘/星座/问答与测测完整设计包及模拟器取证工具
落地 synastry/star/ask API 与 H5 页面,补齐 cece-frontend-re complete-design 证据文档,并加入 Android 模拟器截图抓取脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user