feat: 新增月掩与日月食地理绘图并提升观测计算精度

- 新增月掩恒星和行星:支持搜索、掩甚点、全球掩带及固定地点轨迹计算
- 支持恒星星表坐标转换、有限盘面行星接触事件和月掩 SVG 输出
- 新增日月食及月掩全球投影图、时间标记和 GeoJSON 地理数据接口
- 扩展日食中心线、南北界及偏食足迹采样,支持极区投影
- 修正站心时角、月出月落、月球视半径、折射和恒星自行计算
- 优化内外行星事件搜索、边界选择、极端输入处理和计算稳定性
This commit is contained in:
2026-08-06 12:00:56 +08:00
parent 25dc7ac0bc
commit 9ee2163cc7
137 changed files with 21770 additions and 1746 deletions
+117 -73
View File
@@ -89,16 +89,50 @@ func HMoonHeight(jd, lon, lat, tz float64) float64 {
return HMoonHeightN(jd, lon, lat, tz, -1)
}
type moonObservationState struct {
altitude float64
distanceKM float64
}
func hMoonObservationStateN(jd, lon, lat, tz, height float64, n int) moonObservationState {
calculationJD := TD2UT(jd-tz/24, true)
ra, dec := HMoonTrueRaDecN(calculationJD, n)
distanceKM := HMoonAwayN(calculationJD, n)
distanceAU := distanceKM / angularDiameterAstronomicalUnitKM
topocentricRA, topocentricDec := TopocentricRaDec(ra, dec, lat, lon, jd-tz/24, distanceAU, height)
siderealTime := Limit360(ApparentSiderealTime(jd-tz/24)*15 + lon)
hourAngle := Limit360(siderealTime - topocentricRA)
altitudeSine := Sin(lat)*Sin(topocentricDec) + Cos(topocentricDec)*Cos(lat)*Cos(hourAngle)
return moonObservationState{
altitude: ArcSin(altitudeSine),
distanceKM: distanceKM,
}
}
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)
return hMoonObservationStateN(jd, lon, lat, tz, 0, n).altitude
}
func moonRiseSetResidual(jd, longitude, latitude, timeZone, zenithShift, height float64, n int) float64 {
state := hMoonObservationStateN(jd, longitude, latitude, timeZone, height, n)
// 相对观测者下沉地平线的视上缘高度角 / Apparent upper-limb altitude relative to the observer's depressed horizon.
residual := state.altitude + HeightDegreeByLat(height, latitude)
if zenithShift != 0 {
residual += RefractionFromTrueAltitude(state.altitude, refractionStandardPressureHPa, refractionStandardTemperatureC)
residual += angularSemidiameterArcsec(moonEquatorialRadiusKM, state.distanceKM) / 3600
}
return residual
}
func moonRiseSetOnCivilDay(candidate, slope, civilDayStart, longitude, latitude, originalTimeZone,
localTimeZone, zenithShift, height float64, isRise bool, fallbackErr error) (float64, error) {
if eventRiseSetCandidateValid(candidate, civilDayStart, slope, isRise) {
return candidate, nil
}
return eventDirectionalRiseSetSearch(civilDayStart, isRise, fallbackErr, func(outputJD float64) float64 {
localJD := outputJD + localTimeZone/24 - originalTimeZone/24
return moonRiseSetResidual(localJD, longitude, latitude, localTimeZone, zenithShift, height, -1)
})
}
// 废弃
@@ -109,14 +143,14 @@ func GetMoonTZTime(jd, lon, lat, tz float64) float64 { //实际中天时间{
jd += 0.5
}
estimateJD := jd
for {
prevJD := estimateJD
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
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 stDegree / stDegreep
})
if !ok {
return math.NaN()
}
return estimateJD
}
@@ -133,14 +167,14 @@ func MoonCulminationTime(jde, lon, lat, timezone float64) float64 {
}
return ha
}
for {
prevJD := estimateJD
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
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 stDegree / stDegreep
})
if !ok {
return math.NaN()
}
return estimateJD
}
@@ -155,21 +189,24 @@ func MoonTimeAngle(jd, lon, lat, tz float64) float64 {
}
func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
if !isFiniteFloat(julianDay) || !isFiniteFloat(longitude) || !isFiniteFloat(latitude) || !isFiniteFloat(timeZone) || !isFiniteFloat(zenithShift) || !isFiniteFloat(height) {
return 0, ErrInvalidObservationInput
}
originalTimeZone := timeZone
timeZone = longitude / 15
var timeToMeridian float64
julianDayZero := math.Floor(julianDay) + 0.5
civilDayStart := 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) // 求此时月亮高度
moonResidual := moonRiseSetResidual(julianDay, longitude, latitude, originalTimeZone, zenithShift, height, -1)
moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)
moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)
if moonHeight-moonAngle > 0 { // 月亮在地平线上或在落下与下中天之间
if moonResidual > 0 { // 月亮在地平线上或在落下与下中天之间
if moonAngleTime > 180 {
timeToMeridian = (180 + 360 - moonAngleTime) / 15
} else {
@@ -178,10 +215,10 @@ func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, heig
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
}
if moonHeight-moonAngle < 0 && moonAngleTime > 180 {
if moonResidual < 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 {
} else if moonResidual < 0 && moonAngleTime < 180 {
timeToMeridian = (180 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
}
@@ -191,10 +228,11 @@ func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, heig
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
currentResidual := moonRiseSetResidual(estimatedTime, longitude, latitude, timeZone, zenithShift, height, -1)
if !(currentResidual < -10 && math.Abs(latitude) < 60) {
if currentResidual > 0 {
return moonRiseSetOnCivilDay(math.NaN(), math.NaN(), civilDayStart, longitude, latitude,
originalTimeZone, timeZone, zenithShift, height, true, ErrNeverSet)
}
checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
checkAngle := MoonTimeAngle(checkTime, longitude, latitude, timeZone)
@@ -202,8 +240,9 @@ func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, heig
checkAngle += 360
}
checkTime += (360 - checkAngle) * 4.0 / 60.0 / 24.0
if HMoonHeight(checkTime, longitude, latitude, timeZone) < moonAngle {
return 0, ErrNeverRise
if moonRiseSetResidual(checkTime, longitude, latitude, timeZone, zenithShift, height, -1) < 0 {
return moonRiseSetOnCivilDay(math.NaN(), math.NaN(), civilDayStart, longitude, latitude,
originalTimeZone, timeZone, zenithShift, height, true, ErrNeverRise)
}
}
@@ -215,7 +254,7 @@ func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, heig
estimatedTime += hourAngle/24.00 + hourAngle/33.00/15.00
} else {
i := 0
for MoonHeight(estimatedTime, longitude, latitude, timeZone) < moonAngle {
for moonRiseSetResidual(estimatedTime, longitude, latitude, timeZone, zenithShift, height, -1) < 0 {
i++
estimatedTime += 15.0 / 60.0 / 24.0
if i > 48 {
@@ -225,41 +264,40 @@ func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, heig
}
// 使用牛顿迭代法求精确解
estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)
estimatedTime, slope := moonRiseSetResidualIteration(estimatedTime, longitude, latitude, timeZone, zenithShift, height, 0.00002)
estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24
if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
return 0, ErrNotOnThisDate
}
return estimatedTime, nil
return moonRiseSetOnCivilDay(estimatedTime, slope, civilDayStart, longitude, latitude,
originalTimeZone, timeZone, zenithShift, height, true, nil)
}
func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
if !isFiniteFloat(julianDay) || !isFiniteFloat(longitude) || !isFiniteFloat(latitude) || !isFiniteFloat(timeZone) || !isFiniteFloat(zenithShift) || !isFiniteFloat(height) {
return 0, ErrInvalidObservationInput
}
originalTimeZone := timeZone
timeZone = longitude / 15
var timeToMeridian float64
julianDayZero := math.Floor(julianDay) + 0.5
civilDayStart := 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) // 求此时月亮高度
moonResidual := moonRiseSetResidual(julianDay, longitude, latitude, originalTimeZone, zenithShift, height, -1)
moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)
moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)
if moonHeight-moonAngle < 0 {
if moonResidual < 0 {
timeToMeridian = (360 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24.0*12.0)/15.0/24.0)
}
// 月亮在地平线上或在落下与下中天之间
if moonHeight-moonAngle > 0 && moonAngleTime < 180 {
if moonResidual > 0 && moonAngleTime < 180 {
timeToMeridian = (-moonAngleTime) / 15
estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
} else if moonHeight-moonAngle > 0 {
} else if moonResidual > 0 {
timeToMeridian = (360 - moonAngleTime) / 15
estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
}
@@ -273,16 +311,18 @@ func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, heigh
}
// estimatedTime = 月球中天时间
currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
if !(currentHeight > 10 && math.Abs(latitude) < 60) {
if currentHeight < moonAngle {
return 0, ErrNeverRise
currentResidual := moonRiseSetResidual(estimatedTime, longitude, latitude, timeZone, zenithShift, height, -1)
if !(currentResidual > 10 && math.Abs(latitude) < 60) {
if currentResidual < 0 {
return moonRiseSetOnCivilDay(math.NaN(), math.NaN(), civilDayStart, longitude, latitude,
originalTimeZone, timeZone, zenithShift, height, false, 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
if moonRiseSetResidual(checkTime, longitude, latitude, timeZone, zenithShift, height, -1) > 0 {
return moonRiseSetOnCivilDay(math.NaN(), math.NaN(), civilDayStart, longitude, latitude,
originalTimeZone, timeZone, zenithShift, height, false, ErrNeverSet)
}
}
@@ -294,7 +334,7 @@ func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, heigh
estimatedTime += hourAngle/24 + hourAngle/33.0/15.0
} else {
i := 0
for MoonHeight(estimatedTime, longitude, latitude, timeZone) > moonAngle {
for moonRiseSetResidual(estimatedTime, longitude, latitude, timeZone, zenithShift, height, -1) > 0 {
i++
estimatedTime += 15.0 / 60.0 / 24.0
if i > 48 {
@@ -304,14 +344,10 @@ func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, heigh
}
// 使用牛顿迭代法求精确解
estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)
estimatedTime, slope := moonRiseSetResidualIteration(estimatedTime, longitude, latitude, timeZone, zenithShift, height, 0.00002)
estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24
if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
return 0, ErrNotOnThisDate
}
return estimatedTime, nil
return moonRiseSetOnCivilDay(estimatedTime, slope, civilDayStart, longitude, latitude,
originalTimeZone, timeZone, zenithShift, height, false, nil)
}
// heightFunction 高度函数类型定义,用于牛顿迭代法
@@ -324,24 +360,32 @@ func moonRiseSetNewtonRaphsonIteration(initialTime, longitude, latitude, timeZon
currentTime := initialTime
for {
previousTime := currentTime
// 计算函数值:f(t) = height(t) - targetAngle
var ok bool
currentTime, ok = eventNewtonRefine(currentTime, tolerance, func(previousTime float64) float64 {
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 functionValue / derivative
})
if !ok {
return math.NaN()
}
return currentTime
}
func moonRiseSetResidualIteration(initialTime, longitude, latitude, timeZone, zenithShift, height, tolerance float64) (float64, float64) {
const derivativeStep = 0.000005
slope := math.NaN()
currentTime, ok := eventNewtonRefine(initialTime, tolerance, func(previousTime float64) float64 {
functionValue := moonRiseSetResidual(previousTime, longitude, latitude, timeZone, zenithShift, height, -1)
slope = (moonRiseSetResidual(previousTime+derivativeStep, longitude, latitude, timeZone, zenithShift, height, -1) -
moonRiseSetResidual(previousTime-derivativeStep, longitude, latitude, timeZone, zenithShift, height, -1)) / (2 * derivativeStep)
return functionValue / slope
})
if !ok {
return math.NaN(), math.NaN()
}
return currentTime, slope
}