astro/basic/moon_observation.go
starainrt 3ffdbe0034
feat: 扩展天文计算能力
- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息
- 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算
- 新增木星伽利略卫星位置、现象与接触事件计算
- 新增恒星星表、星座判定、自行修正与观测辅助能力
- 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包
- 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算
- 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试
- 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试
- 更新中文和英文 README,补充示例、精度说明、SVG 配图
2026-05-01 22:38:44 +08:00

348 lines
11 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 (
"math"
. "b612.me/astro/tools"
)
/*
* 月球方位角
*/
func MoonAzimuth(jd, lon, lat, tz float64) float64 {
//tmp := (tz*15 - lon) * 4 / 60
calcjd := TD2UT(jd-tz/24, true)
ra := MoonTrueRa(calcjd)
dec := MoonTrueDec(calcjd)
away := MoonAway(calcjd) / 149597870.7
ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
calcjd = jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - nra)
tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(ndec)*Cos(lat))
azimuth := ArcTan(tmp2)
if azimuth < 0 {
if hourAngle/15 < 12 {
return azimuth + 360
} else {
return azimuth + 180
}
} else {
if hourAngle/15 < 12 {
return azimuth + 180
} else {
return azimuth
}
}
}
func MoonHeight(jd, lon, lat, tz float64) float64 {
// tmp := (tz*15 - lon) * 4 / 60
//truejd=jd-tmp/24;
calcjd := TD2UT(jd-tz/24, true)
ra := MoonTrueRa(calcjd)
dec := MoonTrueDec(calcjd)
away := MoonAway(calcjd) / 149597870.7
ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
calcjd = jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - nra)
tmp2 := Sin(lat)*Sin(ndec) + Cos(ndec)*Cos(lat)*Cos(hourAngle)
return ArcSin(tmp2)
}
func HMoonAzimuth(jd, lon, lat, tz float64) float64 {
return HMoonAzimuthN(jd, lon, lat, tz, -1)
}
func HMoonAzimuthN(jd, lon, lat, tz float64, n int) float64 {
calcjd := TD2UT(jd-tz/24, true)
ra := HMoonTrueRaN(calcjd, n)
dec := HMoonTrueDecN(calcjd, n)
away := HMoonAwayN(calcjd, n) / 149597870.7
ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
calcjd = jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - nra)
tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(ndec)*Cos(lat))
azimuth := ArcTan(tmp2)
if azimuth < 0 {
if hourAngle/15 < 12 {
return azimuth + 360
} else {
return azimuth + 180
}
} else {
if hourAngle/15 < 12 {
return azimuth + 180
} else {
return azimuth
}
}
}
func HMoonHeight(jd, lon, lat, tz float64) float64 {
return HMoonHeightN(jd, lon, lat, tz, -1)
}
func HMoonHeightN(jd, lon, lat, tz float64, n int) float64 {
calcjd := TD2UT(jd-tz/24, true)
ra, dec := HMoonTrueRaDecN(calcjd, n)
away := HMoonAwayN(calcjd, n) / 149597870.7
nra, ndec := TopocentricRaDec(ra, dec, lat, lon, calcjd, away, 0)
calcjd = jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - nra)
tmp2 := Sin(lat)*Sin(ndec) + Cos(ndec)*Cos(lat)*Cos(hourAngle)
return ArcSin(tmp2)
}
// 废弃
func GetMoonTZTime(jd, lon, lat, tz float64) float64 { //实际中天时间{
jd = math.Floor(jd) + 0.5
ttm := MoonTimeAngle(jd, lon, lat, tz)
if ttm > 0 && ttm < 180 {
jd += 0.5
}
estimateJD := jd
for {
prevJD := estimateJD
stDegree := MoonTimeAngle(prevJD, lon, lat, tz) - 359.599
stDegreep := (MoonTimeAngle(prevJD+0.000005, lon, lat, tz) - MoonTimeAngle(prevJD-0.000005, lon, lat, tz)) / 0.00001
estimateJD = prevJD - stDegree/stDegreep
if math.Abs(estimateJD-prevJD) <= 0.00001 {
break
}
}
return estimateJD
}
func MoonCulminationTime(jde, lon, lat, timezone float64) float64 {
//jde 世界时,非力学时,当地时区 0时无需转换力学时
//ra,dec 瞬时天球座标非J2000等时间天球坐标
jde = math.Floor(jde) + 0.5
estimateJD := jde + Limit360(360-MoonTimeAngle(jde, lon, lat, timezone))/15.0/24.0/0.9
limitHA := func(jde, lon, timezone float64) float64 {
ha := MoonTimeAngle(jde, lon, lat, timezone)
if ha < 180 {
ha += 360
}
return ha
}
for {
prevJD := estimateJD
stDegree := limitHA(prevJD, lon, timezone) - 360
stDegreep := (limitHA(prevJD+0.000005, lon, timezone) - limitHA(prevJD-0.000005, lon, timezone)) / 0.00001
estimateJD = prevJD - stDegree/stDegreep
if math.Abs(estimateJD-prevJD) <= 0.00001 {
break
}
}
return estimateJD
}
func MoonTimeAngle(jd, lon, lat, tz float64) float64 {
startime := Limit360(ApparentSiderealTime(jd-tz/24)*15 + lon)
timeangle := startime - HMoonApparentRa(jd, lon, lat, tz)
if timeangle < 0 {
timeangle += 360
}
return timeangle
}
func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
originalTimeZone := timeZone
timeZone = longitude / 15
var timeToMeridian float64
julianDayZero := math.Floor(julianDay) + 0.5
//julianDay = math.Floor(julianDay) + 0.5 - originalTimeZone/24 + timeZone/24 // 求0时JDE
//fix:这里时间分界线应当以传入的时区为准不应当使用当地时区否则在0时的判断会出错
julianDay = math.Floor(julianDay) + 0.5
estimatedTime := julianDay
moonHeight := MoonHeight(julianDay, longitude, latitude, originalTimeZone) // 求此时月亮高度
moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)
moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)
if moonHeight-moonAngle > 0 { // 月亮在地平线上或在落下与下中天之间
if moonAngleTime > 180 {
timeToMeridian = (180 + 360 - moonAngleTime) / 15
} else {
timeToMeridian = (180 - moonAngleTime) / 15
}
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
}
if moonHeight-moonAngle < 0 && moonAngleTime > 180 {
timeToMeridian = (180 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
} else if moonHeight-moonAngle < 0 && moonAngleTime < 180 {
timeToMeridian = (180 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
}
currentAngle := MoonTimeAngle(estimatedTime, longitude, latitude, timeZone)
if math.Abs(currentAngle-180) > 0.5 {
estimatedTime += (180 - currentAngle) * 4.0 / 60.0 / 24.0
}
currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
if !(currentHeight < -10 && math.Abs(latitude) < 60) {
if currentHeight > moonAngle {
return 0, ErrNeverSet
}
checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
checkAngle := MoonTimeAngle(checkTime, longitude, latitude, timeZone)
if checkAngle < 90 {
checkAngle += 360
}
checkTime += (360 - checkAngle) * 4.0 / 60.0 / 24.0
if HMoonHeight(checkTime, longitude, latitude, timeZone) < moonAngle {
return 0, ErrNeverRise
}
}
moonDeclination := MoonApparentDec(estimatedTime, longitude, latitude, timeZone)
tmp := (Sin(moonAngle) - Sin(moonDeclination)*Sin(latitude)) / (Cos(moonDeclination) * Cos(latitude))
if math.Abs(tmp) <= 1 && latitude < 85 {
hourAngle := (180 - ArcCos(tmp)) / 15
estimatedTime += hourAngle/24.00 + hourAngle/33.00/15.00
} else {
i := 0
for MoonHeight(estimatedTime, longitude, latitude, timeZone) < moonAngle {
i++
estimatedTime += 15.0 / 60.0 / 24.0
if i > 48 {
break
}
}
}
// 使用牛顿迭代法求精确解
estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)
estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24
if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
return 0, ErrNotOnThisDate
}
return estimatedTime, nil
}
func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
originalTimeZone := timeZone
timeZone = longitude / 15
var timeToMeridian float64
julianDayZero := math.Floor(julianDay) + 0.5
//julianDay = math.Floor(julianDay) + 0.5 - originalTimeZone/24 + timeZone/24 // 求0时JDE
//fix:这里时间分界线应当以传入的时区为准不应当使用当地时区否则在0时的判断会出错
julianDay = math.Floor(julianDay) + 0.5
estimatedTime := julianDay
moonHeight := MoonHeight(julianDay, longitude, latitude, originalTimeZone) // 求此时月亮高度
moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)
moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)
if moonHeight-moonAngle < 0 {
timeToMeridian = (360 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24.0*12.0)/15.0/24.0)
}
// 月亮在地平线上或在落下与下中天之间
if moonHeight-moonAngle > 0 && moonAngleTime < 180 {
timeToMeridian = (-moonAngleTime) / 15
estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
} else if moonHeight-moonAngle > 0 {
timeToMeridian = (360 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
}
currentAngle := MoonTimeAngle(estimatedTime, longitude, latitude, timeZone)
if currentAngle < 180 {
currentAngle += 360
}
if math.Abs(currentAngle-360) > 0.5 {
estimatedTime += (360 - currentAngle) * 4.0 / 60.0 / 24.0
}
// estimatedTime = 月球中天时间
currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
if !(currentHeight > 10 && math.Abs(latitude) < 60) {
if currentHeight < moonAngle {
return 0, ErrNeverRise
}
checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
angleSubtraction := 180 - MoonTimeAngle(checkTime, longitude, latitude, timeZone)
checkTime += angleSubtraction * 4.0 / 60.0 / 24.0
if HMoonHeight(checkTime, longitude, latitude, timeZone) > moonAngle {
return 0, ErrNeverSet
}
}
moonDeclination := MoonApparentDec(estimatedTime, longitude, latitude, timeZone)
tmp := (Sin(moonAngle) - Sin(moonDeclination)*Sin(latitude)) / (Cos(moonDeclination) * Cos(latitude))
if math.Abs(tmp) <= 1 && latitude < 85 {
hourAngle := (ArcCos(tmp)) / 15.0
estimatedTime += hourAngle/24 + hourAngle/33.0/15.0
} else {
i := 0
for MoonHeight(estimatedTime, longitude, latitude, timeZone) > moonAngle {
i++
estimatedTime += 15.0 / 60.0 / 24.0
if i > 48 {
break
}
}
}
// 使用牛顿迭代法求精确解
estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)
estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24
if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
return 0, ErrNotOnThisDate
}
return estimatedTime, nil
}
// heightFunction 高度函数类型定义,用于牛顿迭代法
type heightFunction func(time, longitude, latitude, timeZone float64) float64
// moonRiseSetNewtonRaphsonIteration 牛顿-拉夫逊迭代法求解天体高度方程
func moonRiseSetNewtonRaphsonIteration(initialTime, longitude, latitude, timeZone, targetAngle float64,
heightFunc heightFunction, tolerance float64) float64 {
const derivativeStep = 0.000005
currentTime := initialTime
for {
previousTime := currentTime
// 计算函数值f(t) = height(t) - targetAngle
functionValue := heightFunc(previousTime, longitude, latitude, timeZone) - targetAngle
// 计算导数f'(t) ≈ (f(t+h) - f(t-h)) / (2h)
derivative := (heightFunc(previousTime+derivativeStep, longitude, latitude, timeZone) -
heightFunc(previousTime-derivativeStep, longitude, latitude, timeZone)) / (2 * derivativeStep)
// 牛顿-拉夫逊公式t_new = t_old - f(t) / f'(t)
currentTime = previousTime - functionValue/derivative
// 检查收敛
if math.Abs(currentTime-previousTime) <= tolerance {
break
}
}
return currentTime
}