feat: 完善日月食与月掩几何链路并扩展历法接口

- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
2026-09-17 12:27:40 +08:00
parent 9ee2163cc7
commit 2bf8478639
428 changed files with 85981 additions and 7998 deletions
+16 -58
View File
@@ -240,40 +240,12 @@ func starOccultationLatitudeEnvelopePass(startTT, endTT float64, star StarCoordi
return minimumLatitude <= limit && maximumLatitude >= -limit
}
func starOccultationGeocentricLongitudeCandidate(startTT, endTT, step float64, star StarCoordinate) float64 {
bestTT := math.NaN()
bestDelta := math.Inf(1)
for tt := startTT; tt <= endTT; tt += step {
delta := math.Abs(signedAngleDifference(HMoonTrueLoN(tt, 8), starOccultationGeocentricStarLongitude(tt, star)))
if delta < bestDelta {
bestDelta = delta
bestTT = tt
}
}
if endTT > startTT {
delta := math.Abs(signedAngleDifference(HMoonTrueLoN(endTT, 8), starOccultationGeocentricStarLongitude(endTT, star)))
if delta < bestDelta {
bestTT = endTT
}
}
return bestTT
}
func starOccultationGeocentricStarLongitude(tt float64, star StarCoordinate) float64 {
ra, dec := starApparentRaDecGeocentric(tt, star)
longitude, _ := RaDecToLoBo(tt, ra, dec)
return longitude
}
func starOccultationMinimizeGeocentricSeparation(seed, startTT, endTT float64, star StarCoordinate) float64 {
halfWindow := 0.75
left := math.Max(startTT, seed-halfWindow)
right := math.Min(endTT, seed+halfWindow)
return starOccultationMinimizeValue(left, right, func(tt float64) float64 {
return starOccultationGeocentricSeparationArcsec(tt, star)
})
}
func starOccultationMinimizeValue(left, right float64, value func(float64) float64) float64 {
if right <= left {
return left
@@ -321,7 +293,15 @@ func starOccultationBestObserver(seedTT, startTT, endTT float64, star StarCoordi
frameAt := func(tt float64) (occultationPathFrame, bool) {
return starOccultationPathFrameAt(tt, star)
}
point, pointOK = occultationPathBoundaryPointForFrame(greatestTT, frameAt, time.UTC)
// 同行星最佳事件搜索:非中心路径的最佳站心取离影轴最近的椭球点,
// 外接触切点位于掩带边缘,只作最后回退。
// Same as the planetary best-event search: for a non-central path the
// best station is the ellipsoid point nearest to the shadow axis, while
// the outer contact tangent on the band edge stays the last resort.
point, pointOK = occultationPathTrackPointForFrame(greatestTT, frameAt, time.UTC)
if !pointOK {
point, pointOK = occultationPathBoundaryPointForFrame(greatestTT, frameAt, time.UTC)
}
}
if !pointOK {
return 0, Observer{}, 0, false
@@ -531,25 +511,6 @@ func starAnnualParallaxEcliptic(tt, longitude, latitude, parallaxMas float64) (f
return normalizeRA(longitude), latitude
}
func starOccultationLongitudeCandidate(startTT, endTT, step float64, star StarCoordinate, observer Observer) float64 {
bestTT := math.NaN()
bestDelta := math.Inf(1)
for tt := startTT; tt <= endTT; tt += step {
delta := starOccultationLongitudeDistance(tt, star, observer)
if delta < bestDelta {
bestDelta = delta
bestTT = tt
}
}
if endTT > startTT {
delta := starOccultationLongitudeDistance(endTT, star, observer)
if delta < bestDelta {
bestTT = endTT
}
}
return bestTT
}
func starOccultationLongitudeDistance(tt float64, star StarCoordinate, observer Observer) float64 {
moonLongitude := HMoonTrueLoN(tt, 8)
starRA, starDec := starApparentRaDec(tt, star, observer)
@@ -557,15 +518,6 @@ func starOccultationLongitudeDistance(tt float64, star StarCoordinate, observer
return math.Abs(signedAngleDifference(moonLongitude, starLongitude))
}
func starOccultationMinimizeSeparation(seed, startTT, endTT float64, star StarCoordinate, observer Observer) float64 {
halfWindow := 0.75
left := math.Max(startTT, seed-halfWindow)
right := math.Min(endTT, seed+halfWindow)
return starOccultationMinimizeValue(left, right, func(tt float64) float64 {
return starMoonSeparationArcsec(tt, star, observer)
})
}
func starMoonSeparationArcsec(tt float64, star StarCoordinate, observer Observer) float64 {
position := starMoonPositionAt(tt, star, observer)
if !position.valid {
@@ -675,7 +627,13 @@ func occultationPositionAngle(moonRA, moonDec, starRA, starDec float64) float64
}
func occultationAltitude(tt float64, observer Observer, ra, dec float64) float64 {
hourAngle := signedAngleDifference(ApparentSiderealTime(TD2UT(tt, false))*15+observer.Longitude, ra) * math.Pi / 180
return occultationAltitudeWithSidereal(
ApparentSiderealTime(TD2UT(tt, false))*15, observer, ra, dec,
)
}
func occultationAltitudeWithSidereal(siderealDegrees float64, observer Observer, ra, dec float64) float64 {
hourAngle := signedAngleDifference(siderealDegrees+observer.Longitude, ra) * math.Pi / 180
lat := observer.Latitude * math.Pi / 180
declination := dec * math.Pi / 180
sinAltitude := math.Sin(lat)*math.Sin(declination) + math.Cos(lat)*math.Cos(declination)*math.Cos(hourAngle)