Files
astro/basic/star.go
T
b612 16c62a97d5 feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持
- 改进日月食、月掩、行星事件及路径边界计算
- 完善恒星三维自行与动态距离传播
- 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具
- 整理中英文手册、示例资源及回归测试
2026-09-23 18:55:12 +08:00

218 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package basic
import (
. "b612.me/astro/tools"
"math"
)
// StarHeight 星体的高度角
// 传入 jde时间、瞬时赤经、瞬时赤纬、经度、纬度、时区,jde时间应为时区时间
// 返回高度角,单位为度
func StarHeight(localJD, ra, dec, lon, lat, timezone float64) float64 {
// 转换为世界时
utcJD := localJD - timezone/24.0
// 计算视恒星时
st := Limit360(ApparentSiderealTime(UTC2UT1(utcJD))*15 + lon)
// 计算时角
hourAngle := Limit360(st - ra)
// 高度角、时角与天球座标三角转换公式
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(hourAngle)
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
return ArcSin(sinHeight)
}
// StarAzimuth 星体的方位角
// 传入 jde时间、瞬时赤经、瞬时赤纬、经度、纬度、时区,jde时间应为时区时间
// 返回方位角,单位为度,正北为0,度数顺时针增加,取值范围[0-360)
func StarAzimuth(localJD, ra, dec, lon, lat, timezone float64) float64 {
// 转换为世界时
utcJD := localJD - timezone/24.0
// 计算视恒星时
st := Limit360(ApparentSiderealTime(UTC2UT1(utcJD))*15 + lon)
// 计算时角
hourAngle := Limit360(st - ra)
// 三角转换公式
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
azimuth := ArcTan(tanAzimuth)
if azimuth < 0 {
if hourAngle/15 < 12 {
return azimuth + 360
}
return azimuth + 180
}
if hourAngle/15 < 12 {
return azimuth + 180
}
return azimuth
}
// StarHourAngle 星体的时角
// 传入 jde时间、瞬时赤经、瞬时赤纬、经度、时区,jde时间应为时区时间
// 返回时角
func StarHourAngle(localJD, ra, lon, timezone float64) float64 {
// 转换为世界时
utcJD := localJD - timezone/24.0
// 计算视恒星时
st := Limit360(ApparentSiderealTime(UTC2UT1(utcJD))*15 + lon)
// 计算时角
return Limit360(st - ra)
}
// MeanSiderealTime 平恒星时
func MeanSiderealTime(jd float64) float64 {
return MeanSiderealTime2006(jd)
}
// ApparentSiderealTime 视恒星时,计算章动
func ApparentSiderealTime(jd float64) float64 {
return ApparentSiderealTime2006(jd)
}
// MeanSiderealTime1982 不含章动下的恒星时
func MeanSiderealTime1982(jd float64) float64 {
t := (jd - 2451545) / 36525
return (Limit360(280.46061837+360.98564736629*(jd-2451545.0)+0.000387933*t*t-t*t*t/38710000) / 15)
}
// ApparentSiderealTime1982 视恒星时,计算章动
func ApparentSiderealTime1982(jd float64) float64 {
tmp := MeanSiderealTime1982(jd)
dpsi, deps := Nutation2000B(jd)
return tmp + dpsi*math.Cos((Obliquity1980(jd)+deps)*math.Pi/180)/15
}
// EarthRotationAngle 计算地球自转角 (ERA)
// jd_ut1: UT1 时间的儒略日
// 返回值: 地球自转角 (弧度)
func EarthRotationAngle(jd_ut1 float64) float64 {
t := jd_ut1 - 2451545.0
frac := math.Mod(jd_ut1, 1.0)
era := math.Mod(math.Pi*2*(0.7790572732640+0.00273781191135448*t+frac), math.Pi*2)
if era < 0 {
era += math.Pi * 2
}
return era
}
// MeanSiderealTime2006 计算格林尼治平恒星时 (GMST)
// jd_ut1: UT1 时间的儒略日
// jd_tt: TT 时间的儒略日
// 返回值: 格林尼治平恒星时 (弧度)
func MeanSiderealTime2006(jd_ut1 float64) float64 {
jd_tt := UT12TT(jd_ut1)
t := (jd_tt - 2451545.0) / 36525.0
era := EarthRotationAngle(jd_ut1)
// 公式 2.12
gmst := math.Mod(era+(0.014506+4612.15739966*t+1.39667721*t*t+
-0.00009344*t*t*t+0.00001882*t*t*t*t)/60/60*math.Pi/180, math.Pi*2)
if gmst < 0 {
gmst += math.Pi * 2
}
return gmst * deg / 15
}
// ApparentSiderealTime2006 视恒星时,计算章动。
// 一次求值只要一份 IAU2000B 章动(黄经与交角在同一次展开里同时得到),并走有界记忆表,
// 避免月掩路径里的重复求值;ΔT 覆盖会使记忆表按世代失效。见 sidereal_memo.go。
// ApparentSiderealTime2006 computes apparent sidereal time. One evaluation needs only a single
// IAU2000B nutation expansion (longitude and obliquity come out of the same series) and is
// memoized so repeated occultation-path queries do not rerun it; a ΔT override invalidates the
// memo by generation. See sidereal_memo.go.
func ApparentSiderealTime2006(jd float64) float64 {
if value, ok := siderealMemoLoad(jd); ok {
return value
}
generation := siderealMemoGeneration()
tmp := MeanSiderealTime2006(jd)
dpsi, deps := Nutation2000B(jd)
value := tmp + dpsi*math.Cos((Obliquity1980(jd)+deps)*math.Pi/180)/15
siderealMemoStore(jd, value, generation)
return value
}
func StarRiseTime(localJD, ra, dec, lon, lat, height, timezone float64, aero bool) (float64, error) {
return StarRiseSetTime(localJD, ra, dec, lon, lat, height, timezone, aero, true)
}
func StarSetTime(localJD, ra, dec, lon, lat, height, timezone float64, aero bool) (float64, error) {
return StarRiseSetTime(localJD, ra, dec, lon, lat, height, timezone, aero, false)
}
func StarRiseSetTime(localJD, ra, dec, lon, lat, height, timezone float64, aero, isRise bool) (float64, error) {
if !isFiniteFloat(localJD) || !isFiniteFloat(ra) || !isFiniteFloat(dec) || !isFiniteFloat(lon) || !isFiniteFloat(lat) || !isFiniteFloat(height) || !isFiniteFloat(timezone) {
return 0, ErrInvalidObservationInput
}
// localJD 是本地民用日锚点(当地 0 时),不是力学时。
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
localJD = math.Floor(localJD) + 0.5
targetAltitude := StandardAltitudeStar(aero, height, lat)
sct := StarCulminationTime(localJD, ra, lon, timezone)
tmp := (Sin(targetAltitude) - Sin(dec)*Sin(lat)) / (Cos(dec) * Cos(lat))
if math.Abs(tmp) > 1 {
if StarHeight(sct, ra, dec, lon, lat, timezone) < 0 {
return 0, ErrNeverRise
}
return 0, ErrNeverSet
}
var estimateJD float64
if isRise {
estimateJD = sct - ArcCos(tmp)/15.0/24.0
} else {
estimateJD = sct + ArcCos(tmp)/15.0/24.0
}
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
stDegree := StarHeight(prevJD, ra, dec, lon, lat, timezone) - targetAltitude
stDegreep := (StarHeight(prevJD+0.000005, ra, dec, lon, lat, timezone) - StarHeight(prevJD-0.000005, ra, dec, lon, lat, timezone)) / 0.00001
return stDegree / stDegreep
})
if !ok {
return 0, ErrInvalidObservationInput
}
return estimateJD, nil
}
func StarCulminationTime(localJD, ra, lon, timezone float64) float64 {
if !isFiniteFloat(localJD) || !isFiniteFloat(ra) || !isFiniteFloat(lon) || !isFiniteFloat(timezone) {
return math.NaN()
}
// localJD 是本地民用日锚点(当地 0 时),不是力学时。
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
localJD = math.Floor(localJD) + 0.5
estimateJD := localJD + Limit360(360-StarHourAngle(localJD, ra, lon, timezone))/15.0/24.0*0.99726851851851851851
limitStarHA := func(localJD, ra, lon, timezone float64) float64 {
ha := StarHourAngle(localJD, ra, lon, timezone)
if ha < 180 {
ha += 360
}
return ha
}
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
stDegree := limitStarHA(prevJD, ra, lon, timezone) - 360
stDegreep := (limitStarHA(prevJD+0.000005, ra, lon, timezone) - limitStarHA(prevJD-0.000005, ra, lon, timezone)) / 0.00001
return stDegree / stDegreep
})
if !ok {
return math.NaN()
}
return estimateJD
}
func StarAngularSeparation(ra1, dec1, ra2, dec2 float64) float64 {
//cos(d)=sinδ1 sinδ2 + cosδ1 cosδ2 cos(α1-α2)
d := Sin(dec1)*Sin(dec2) + Cos(dec1)*Cos(dec2)*Cos(ra1-ra2)
if math.Abs(d) >= 0.999999997 {
//d = √(Δα*cosδ)2+(Δδ)2
tmp1 := ((ra1 - ra2) * Cos((dec1+dec2)/2))
tmp2 := (dec1 - dec2)
return math.Sqrt(tmp1*tmp1 + tmp2*tmp2)
}
return ArcCos(d)
}