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