feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
@@ -3,6 +3,8 @@ package basic
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"b612.me/astro/internal/geodata"
|
||||
)
|
||||
|
||||
func (solver solarEclipseSolver) magnitudeContourSegments(
|
||||
@@ -24,6 +26,8 @@ func (solver solarEclipseSolver) magnitudeContourSegmentsWithSpacing(
|
||||
if targetSpacingKM <= 0 || math.IsNaN(targetSpacingKM) || math.IsInf(targetSpacingKM, 0) {
|
||||
targetSpacingKM = solarEclipseMagnitudeContourTargetSpacingKM
|
||||
}
|
||||
// 追踪用事件局部插值星历求残差与雅可比,输出点仍由 validMagnitudeArcState 精确确认。
|
||||
solver = solver.withLocalEphemeris()
|
||||
var transitions []SolarEclipsePathPoint
|
||||
if hybrid && math.Abs(magnitude-1) <= 1e-12 {
|
||||
transitions = solver.centralMagnitudeOneTransitionsInInterval(
|
||||
@@ -527,7 +531,7 @@ func (solver solarEclipseSolver) validMagnitudeArcState(
|
||||
iterations int,
|
||||
) (solarEclipseMagnitudeArcState, int, bool) {
|
||||
jde := referenceJDE + coordinates[2]/solarEclipseMagnitudeContourTimeScale
|
||||
longitude := normalizeLongitude(coordinates[0])
|
||||
longitude := geodata.NormalizeLongitude(coordinates[0])
|
||||
latitude := coordinates[1]
|
||||
evaluation := solver.magnitudeEvaluationAt(jde)
|
||||
state := evaluation.center.stateAt(longitude*rad, latitude*rad, 0)
|
||||
@@ -553,9 +557,9 @@ func (solver solarEclipseSolver) magnitudeEnvelopeJacobian(
|
||||
magnitude, referenceJDE float64,
|
||||
) ([2]float64, [2][3]float64, bool) {
|
||||
jde := referenceJDE + coordinates[2]/solarEclipseMagnitudeContourTimeScale
|
||||
longitude := normalizeLongitude(coordinates[0])
|
||||
longitude := geodata.NormalizeLongitude(coordinates[0])
|
||||
latitude := coordinates[1]
|
||||
evaluation := solver.magnitudeEvaluationAt(jde)
|
||||
evaluation := solver.magnitudeCandidateEvaluationAt(jde)
|
||||
residual, ok := solarEclipseMagnitudeEnvelopeResidualAt(evaluation, longitude, latitude, magnitude)
|
||||
if !ok {
|
||||
return [2]float64{}, [2][3]float64{}, false
|
||||
@@ -574,14 +578,14 @@ func (solver solarEclipseSolver) magnitudeEnvelopeJacobian(
|
||||
jacobian[row][column] = (shiftedResidual[row] - residual[row]) / steps[column]
|
||||
}
|
||||
}
|
||||
timeEvaluation := solver.magnitudeEvaluationAt(jde + steps[2]/solarEclipseMagnitudeContourTimeScale)
|
||||
timeEvaluation := solver.magnitudeCandidateEvaluationAt(jde + steps[2]/solarEclipseMagnitudeContourTimeScale)
|
||||
timeResidual, timeOK := solarEclipseMagnitudeEnvelopeResidualAt(
|
||||
timeEvaluation, longitude, latitude, magnitude,
|
||||
)
|
||||
if !timeOK {
|
||||
return [2]float64{}, [2][3]float64{}, false
|
||||
}
|
||||
beforeEvaluation := solver.magnitudeEvaluationAt(jde - steps[2]/solarEclipseMagnitudeContourTimeScale)
|
||||
beforeEvaluation := solver.magnitudeCandidateEvaluationAt(jde - steps[2]/solarEclipseMagnitudeContourTimeScale)
|
||||
beforeResidual, beforeOK := solarEclipseMagnitudeEnvelopeResidualAt(
|
||||
beforeEvaluation, longitude, latitude, magnitude,
|
||||
)
|
||||
@@ -638,7 +642,7 @@ func (solver solarEclipseSolver) refineMagnitudeHorizonCrossing(
|
||||
deltaLongitude := math.Remainder(hidden.Longitude-visible.Longitude, 360)
|
||||
return solver.refineMagnitudeHorizonPoint(
|
||||
visible.JDE+fraction*(hidden.JDE-visible.JDE),
|
||||
normalizeLongitude(visible.Longitude+fraction*deltaLongitude),
|
||||
geodata.NormalizeLongitude(visible.Longitude+fraction*deltaLongitude),
|
||||
visible.Latitude+fraction*(hidden.Latitude-visible.Latitude),
|
||||
magnitude,
|
||||
)
|
||||
@@ -688,7 +692,7 @@ func (solver solarEclipseSolver) refineMagnitudeHorizonPoint(
|
||||
if math.Abs(delta[2]) > 10.0/1440.0 {
|
||||
delta[2] = math.Copysign(10.0/1440.0, delta[2])
|
||||
}
|
||||
longitude = normalizeLongitude(longitude + delta[0])
|
||||
longitude = geodata.NormalizeLongitude(longitude + delta[0])
|
||||
latitude += delta[1]
|
||||
jde += delta[2]
|
||||
}
|
||||
@@ -725,14 +729,14 @@ func (solver solarEclipseSolver) magnitudeCandidateEvaluationAt(jde float64) sol
|
||||
|
||||
func (solver solarEclipseSolver) localStateContextAt(jde float64) localSolarEclipseStateContext {
|
||||
if solver.localStateContextCache == nil {
|
||||
return newLocalSolarEclipseStateContextWithOverride(jde, solver.deltaTSeconds, solver.params)
|
||||
return newLocalSolarEclipseStateContextWithOverride(jde, solver.deltaTSeconds, solver.params, solver.inst)
|
||||
}
|
||||
key := math.Float64bits(jde)
|
||||
if context, ok := solver.localStateContextCache[key]; ok &&
|
||||
context.generation == deltaTGenerationValue() {
|
||||
return context
|
||||
}
|
||||
context := newLocalSolarEclipseStateContextWithOverride(jde, solver.deltaTSeconds, solver.params)
|
||||
context := newLocalSolarEclipseStateContextWithOverride(jde, solver.deltaTSeconds, solver.params, solver.inst)
|
||||
return storeLocalSolarEclipseStateContext(solver.localStateContextCache, key, context)
|
||||
}
|
||||
|
||||
@@ -756,19 +760,28 @@ func storeLocalSolarEclipseStateContext(
|
||||
}
|
||||
|
||||
func (solver solarEclipseSolver) localStateContextCandidateAt(jde float64) localSolarEclipseStateContext {
|
||||
if solver.localEphemeris == nil {
|
||||
// 有月缘时坐标架也取插值表;插值表缺失或越界时退回精确上下文。
|
||||
if solver.localEphemeris == nil || (solver.params.lunarLimb != nil && solver.limbAxes == nil) {
|
||||
return solver.localStateContextAt(jde)
|
||||
}
|
||||
sun, moon, ok := solver.localEphemeris.equatorialAt(jde)
|
||||
if !ok {
|
||||
return solver.localStateContextAt(jde)
|
||||
}
|
||||
return localSolarEclipseStateContext{
|
||||
context := localSolarEclipseStateContext{
|
||||
sunXYZ: solarEclipseLLRToXYZ(sun[0], sun[1], sun[2]),
|
||||
moonXYZ: solarEclipseLLRToXYZ(moon[0], moon[1], moon[2]),
|
||||
gst: solver.siderealTimeAt(jde),
|
||||
params: solver.params,
|
||||
}
|
||||
if solver.params.lunarLimb != nil {
|
||||
frame, ok := solver.limbAxes.frameAt(jde)
|
||||
if !ok {
|
||||
return solver.localStateContextAt(jde)
|
||||
}
|
||||
context.limbFrame = &frame
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
func (solver solarEclipseSolver) magnitudeHorizonResidual(
|
||||
@@ -924,7 +937,7 @@ func (solver solarEclipseSolver) magnitudeContourGeographicSeedsAt(jde, magnitud
|
||||
// Reuse the global greatest-eclipse coordinates as the bounded seed for
|
||||
// the local envelope solver instead of treating the missing central axis
|
||||
// as evidence that every magnitude contour is absent.
|
||||
result := solarEclipse(solver.newMoonJDE, solver.model)
|
||||
result := solarEclipse(solver.newMoonJDE, solver.model, solver.inst)
|
||||
if !result.HasPartial || result.GreatestEclipse == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -972,7 +985,7 @@ func (solver solarEclipseSolver) magnitudeContourGeographicSeedsAt(jde, magnitud
|
||||
centerVector[1]*math.Cos(angle) + direction[1]*math.Sin(angle),
|
||||
centerVector[2]*math.Cos(angle) + direction[2]*math.Sin(angle),
|
||||
}
|
||||
longitude := normalizeLongitude(math.Atan2(pointVector[1], pointVector[0]) / rad)
|
||||
longitude := geodata.NormalizeLongitude(math.Atan2(pointVector[1], pointVector[0]) / rad)
|
||||
latitude := math.Asin(math.Max(-1, math.Min(1, pointVector[2]))) / rad
|
||||
state := evaluation.center.stateAt(longitude*rad, latitude*rad, 0)
|
||||
value := solarEclipseMagnitudeAtTarget(state, magnitude) - magnitude
|
||||
@@ -992,7 +1005,7 @@ func (solver solarEclipseSolver) magnitudeContourGeographicSeedsAt(jde, magnitud
|
||||
centerVector[1]*math.Cos(middleAngle) + direction[1]*math.Sin(middleAngle),
|
||||
centerVector[2]*math.Cos(middleAngle) + direction[2]*math.Sin(middleAngle),
|
||||
}
|
||||
middleLongitude := normalizeLongitude(math.Atan2(middleVector[1], middleVector[0]) / rad)
|
||||
middleLongitude := geodata.NormalizeLongitude(math.Atan2(middleVector[1], middleVector[0]) / rad)
|
||||
middleLatitude := math.Asin(math.Max(-1, math.Min(1, middleVector[2]))) / rad
|
||||
middleState := evaluation.center.stateAt(middleLongitude*rad, middleLatitude*rad, 0)
|
||||
middleValue := solarEclipseMagnitudeAtTarget(middleState, magnitude) - magnitude
|
||||
@@ -1012,7 +1025,7 @@ func (solver solarEclipseSolver) magnitudeContourGeographicSeedsAt(jde, magnitud
|
||||
centerVector[1]*math.Cos(seedAngle) + direction[1]*math.Sin(seedAngle),
|
||||
centerVector[2]*math.Cos(seedAngle) + direction[2]*math.Sin(seedAngle),
|
||||
}
|
||||
seedLongitude := normalizeLongitude(math.Atan2(seedVector[1], seedVector[0]) / rad)
|
||||
seedLongitude := geodata.NormalizeLongitude(math.Atan2(seedVector[1], seedVector[0]) / rad)
|
||||
seedLatitude := math.Asin(math.Max(-1, math.Min(1, seedVector[2]))) / rad
|
||||
seedLongitude, seedLatitude, refined := solver.refineMagnitudeEnvelopePoint(
|
||||
magnitude, seedLongitude, seedLatitude, evaluation,
|
||||
@@ -1160,7 +1173,7 @@ func (solver solarEclipseSolver) magnitudeContourPointAt(
|
||||
return SolarEclipsePathPoint{}, false
|
||||
}
|
||||
longitude, latitude := solarEclipseIntersectionGeodetic(intersection, axis)
|
||||
sunAltitudeRad := solarEclipseSunAltitudeAtGreatest(jde, longitude, latitude, axis.gst)
|
||||
sunAltitudeRad := solarEclipseSunAltitudeAtGreatest(jde, longitude, latitude, axis.gst, solver.inst)
|
||||
return SolarEclipsePathPoint{
|
||||
JDE: jde,
|
||||
Longitude: longitude,
|
||||
|
||||
Reference in New Issue
Block a user