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,161 @@
|
||||
// Package ephemeris computes tropical ecliptic longitudes.
|
||||
// Default backend: Swiss Ephemeris Moshier (no external ephe files).
|
||||
// NOTE: go-swisseph is AGPL-3.0; commercial deployment needs Astrodienst SE license or AGPL compliance.
|
||||
package ephemeris
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
swe "github.com/tejzpr/go-swisseph"
|
||||
)
|
||||
|
||||
// Body keys match natal chart keys.
|
||||
const (
|
||||
BodySun = "sun"
|
||||
BodyMoon = "moon"
|
||||
BodyMercury = "mercury"
|
||||
BodyVenus = "venus"
|
||||
BodyMars = "mars"
|
||||
BodyJupiter = "jupiter"
|
||||
BodySaturn = "saturn"
|
||||
BodyUranus = "uranus"
|
||||
BodyNeptune = "neptune"
|
||||
BodyPluto = "pluto"
|
||||
)
|
||||
|
||||
var bodyID = map[string]int32{
|
||||
BodySun: swe.Sun,
|
||||
BodyMoon: swe.Moon,
|
||||
BodyMercury: swe.Mercury,
|
||||
BodyVenus: swe.Venus,
|
||||
BodyMars: swe.Mars,
|
||||
BodyJupiter: swe.Jupiter,
|
||||
BodySaturn: swe.Saturn,
|
||||
BodyUranus: swe.Uranus,
|
||||
BodyNeptune: swe.Neptune,
|
||||
BodyPluto: swe.Pluto,
|
||||
}
|
||||
|
||||
// PlanetOrder is the standard body sequence (excluding ASC).
|
||||
var PlanetOrder = []string{
|
||||
BodySun, BodyMoon, BodyMercury, BodyVenus, BodyMars,
|
||||
BodyJupiter, BodySaturn, BodyUranus, BodyNeptune, BodyPluto,
|
||||
}
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
iflag int32 = swe.FlagMoseph
|
||||
epheOK bool
|
||||
)
|
||||
|
||||
func initSE() {
|
||||
once.Do(func() {
|
||||
path := os.Getenv("SE_EPHE_PATH")
|
||||
if path == "" {
|
||||
path = os.Getenv("EPHEMERIS_PATH")
|
||||
}
|
||||
if path != "" {
|
||||
swe.SetEphePath(path)
|
||||
iflag = swe.FlagSwieph
|
||||
epheOK = true
|
||||
} else {
|
||||
// Moshier: no files required
|
||||
iflag = swe.FlagMoseph
|
||||
epheOK = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Backend returns "moshier" or "swiss" for diagnostics.
|
||||
func Backend() string {
|
||||
initSE()
|
||||
if iflag == swe.FlagSwieph {
|
||||
return "swiss"
|
||||
}
|
||||
return "moshier"
|
||||
}
|
||||
|
||||
// JulianDayUT converts a UTC instant to Julian Day (UT).
|
||||
func JulianDayUT(utc time.Time) float64 {
|
||||
initSE()
|
||||
utc = utc.UTC()
|
||||
sec := float64(utc.Second()) + float64(utc.Nanosecond())/1e9
|
||||
dret, err := swe.UtcToJd(
|
||||
int32(utc.Year()), int32(utc.Month()), int32(utc.Day()),
|
||||
int32(utc.Hour()), int32(utc.Minute()), sec, swe.GregCal,
|
||||
)
|
||||
if err != nil {
|
||||
// Fallback Meeus-style JD
|
||||
return julianDayFallback(utc)
|
||||
}
|
||||
return dret[1] // UT
|
||||
}
|
||||
|
||||
// PlanetLon returns tropical ecliptic longitude in degrees [0,360).
|
||||
func PlanetLon(jdUt float64, body string) (float64, error) {
|
||||
initSE()
|
||||
ipl, ok := bodyID[body]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unknown body %q", body)
|
||||
}
|
||||
r := swe.CalcUT(jdUt, ipl, iflag)
|
||||
if r.Flag < 0 {
|
||||
return 0, fmt.Errorf("swe calc %s: %s", body, r.Error)
|
||||
}
|
||||
return norm360(r.Data[0]), nil
|
||||
}
|
||||
|
||||
// Ascendant returns tropical ASC longitude degrees using Placidus cusps (Points[0]).
|
||||
// Callers apply Whole Sign houses from this ASC.
|
||||
func Ascendant(jdUt, lat, lng float64) (float64, error) {
|
||||
initSE()
|
||||
h := swe.Houses(jdUt, lat, lng, 'P')
|
||||
if h.Flag < 0 || len(h.Points) < 1 {
|
||||
return 0, fmt.Errorf("swe houses failed flag=%d", h.Flag)
|
||||
}
|
||||
return norm360(h.Points[0]), nil
|
||||
}
|
||||
|
||||
// AllPlanetLons returns longitudes for PlanetOrder.
|
||||
func AllPlanetLons(jdUt float64) (map[string]float64, error) {
|
||||
out := make(map[string]float64, len(PlanetOrder))
|
||||
for _, k := range PlanetOrder {
|
||||
lon, err := PlanetLon(jdUt, k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[k] = lon
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func norm360(x float64) float64 {
|
||||
x = math.Mod(x, 360)
|
||||
if x < 0 {
|
||||
x += 360
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func julianDayFallback(t time.Time) float64 {
|
||||
y := t.Year()
|
||||
m := int(t.Month())
|
||||
d := float64(t.Day()) + float64(t.Hour())/24 + float64(t.Minute())/1440 + float64(t.Second())/86400
|
||||
if m <= 2 {
|
||||
y--
|
||||
m += 12
|
||||
}
|
||||
A := y / 100
|
||||
B := 2 - A + A/4
|
||||
return math.Floor(365.25*float64(y+4716)) + math.Floor(30.6001*float64(m+1)) + d + float64(B) - 1524.5
|
||||
}
|
||||
|
||||
// Ready reports whether ephemeris init succeeded (always true for Moshier).
|
||||
func Ready() bool {
|
||||
initSE()
|
||||
return epheOK
|
||||
}
|
||||
Reference in New Issue
Block a user