2026-09-17 12:27:40 +08:00
|
|
|
package basic
|
|
|
|
|
|
|
|
|
|
import "math"
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
occultationDenseEphemerisNodeCount = 193
|
|
|
|
|
occultationDenseEphemerisStepDays = 30.0 / 1440.0
|
|
|
|
|
occultationDenseEphemerisTolerance = 5e-11
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Width extrema near a limb can change branches under tiny frame differences.
|
|
|
|
|
// Keep this metadata on the full-term model without moving the traced points.
|
|
|
|
|
func correctOccultationCenterWidths(points []OccultationPathPoint, frameAt occultationPathFrameFunc) {
|
|
|
|
|
for index := range points {
|
|
|
|
|
_, _, width, ok := occultationPathLimitsAndWidthForFrame(centerTimeTT(points[index].Time), frameAt)
|
|
|
|
|
points[index].WidthKM = 0
|
|
|
|
|
if ok {
|
|
|
|
|
points[index].WidthKM = width
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cache *starOccultationEventCache) preparePathEphemeris(center float64, algorithm OccultationPathAlgorithm) {
|
|
|
|
|
if algorithm != OccultationPathAlgorithmExact {
|
|
|
|
|
nodes := newDenseOccultationEphemerisNodes(center, func(tt float64) ([3]float64, [3]float64) {
|
|
|
|
|
state := starOccultationEphemerisStateAt(tt, cache.star)
|
|
|
|
|
moon := occultationPathRaDecVector(state.moonRA, state.moonDec, state.moonDistanceKM)
|
|
|
|
|
distance := state.starDistanceKM
|
|
|
|
|
if distance <= 0 {
|
|
|
|
|
distance = 1
|
|
|
|
|
}
|
|
|
|
|
target := occultationPathRaDecVector(state.starRA, state.starDec, distance)
|
|
|
|
|
return [3]float64{moon.x, moon.y, moon.z}, [3]float64{target.x, target.y, target.z}
|
|
|
|
|
})
|
|
|
|
|
if len(nodes) > 0 {
|
2026-09-23 18:55:12 +08:00
|
|
|
cache.local = newStarOccultationLocalEphemerisFromNodes(center, cache.star, nodes, true)
|
2026-09-17 12:27:40 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cache.prepareLocalEphemeris(center)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cache *planetOccultationEventCache) preparePathEphemeris(center float64, algorithm OccultationPathAlgorithm) {
|
|
|
|
|
if algorithm != OccultationPathAlgorithmExact {
|
|
|
|
|
nodes := newDenseOccultationEphemerisNodes(center, func(tt float64) ([3]float64, [3]float64) {
|
|
|
|
|
state := planetOccultationEphemerisStateAt(tt, cache.config)
|
|
|
|
|
moon := occultationPathRaDecVector(state.moonRA, state.moonDec, state.moonDistanceKM)
|
|
|
|
|
target := occultationPathRaDecVector(state.planetRA, state.planetDec, state.planetDistanceKM)
|
|
|
|
|
return [3]float64{moon.x, moon.y, moon.z}, [3]float64{target.x, target.y, target.z}
|
|
|
|
|
})
|
|
|
|
|
if len(nodes) > 0 {
|
|
|
|
|
cache.local = &planetOccultationLocalEphemeris{nodes: nodes, dense: true}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cache.prepareLocalEphemeris(center)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Midpoint checks reject inaccurate or invalid tables before any path state
|
|
|
|
|
// is cached. They are sampled safeguards, not a rigorous global error bound.
|
|
|
|
|
func newDenseOccultationEphemerisNodes(
|
|
|
|
|
center float64,
|
|
|
|
|
sample func(float64) ([3]float64, [3]float64),
|
|
|
|
|
) []localEphemerisVectorNode {
|
|
|
|
|
if !finite(center) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nodes := make([]localEphemerisVectorNode, occultationDenseEphemerisNodeCount)
|
|
|
|
|
for index := range nodes {
|
|
|
|
|
tt := center + float64(index-len(nodes)/2)*occultationDenseEphemerisStepDays
|
|
|
|
|
first, next := sample(tt)
|
|
|
|
|
if !finiteVector3(first) || !finiteVector3(next) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
nodes[index] = localEphemerisVectorNode{tt: tt, first: first, next: next}
|
|
|
|
|
}
|
|
|
|
|
for index := 1; index < len(nodes); index++ {
|
|
|
|
|
tt := (nodes[index-1].tt + nodes[index].tt) / 2
|
|
|
|
|
first, next, ok := interpolateDenseOccultationVectors(nodes, tt)
|
|
|
|
|
exactFirst, exactNext := sample(tt)
|
|
|
|
|
if !ok || !denseOccultationVectorAccurate(first, exactFirst) || !denseOccultationVectorAccurate(next, exactNext) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nodes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func denseOccultationVectorAccurate(approximate, exact [3]float64) bool {
|
|
|
|
|
if !finiteVector3(approximate) || !finiteVector3(exact) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
norm, difference := 0.0, 0.0
|
|
|
|
|
for index := range exact {
|
|
|
|
|
norm += exact[index] * exact[index]
|
|
|
|
|
delta := approximate[index] - exact[index]
|
|
|
|
|
difference += delta * delta
|
|
|
|
|
}
|
|
|
|
|
return norm > 0 && finite(norm) && difference <= norm*occultationDenseEphemerisTolerance*occultationDenseEphemerisTolerance
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func interpolateDenseOccultationVectors(
|
|
|
|
|
nodes []localEphemerisVectorNode,
|
|
|
|
|
tt float64,
|
|
|
|
|
) ([3]float64, [3]float64, bool) {
|
|
|
|
|
const points = 6
|
|
|
|
|
if len(nodes) < points || !finite(tt) || tt < nodes[0].tt || tt > nodes[len(nodes)-1].tt {
|
|
|
|
|
return [3]float64{}, [3]float64{}, false
|
|
|
|
|
}
|
|
|
|
|
step := nodes[1].tt - nodes[0].tt
|
|
|
|
|
if !finite(step) || step <= 0 {
|
|
|
|
|
return [3]float64{}, [3]float64{}, false
|
|
|
|
|
}
|
|
|
|
|
start := int(math.Floor((tt-nodes[0].tt)/step)) - 2
|
|
|
|
|
if start < 0 {
|
|
|
|
|
start = 0
|
|
|
|
|
}
|
|
|
|
|
if start > len(nodes)-points {
|
|
|
|
|
start = len(nodes) - points
|
|
|
|
|
}
|
|
|
|
|
var first, next [3]float64
|
|
|
|
|
for point := 0; point < points; point++ {
|
|
|
|
|
weight := 1.0
|
|
|
|
|
// Rounded Julian days are not exactly uniformly spaced. Preserve the
|
|
|
|
|
// actual node times, leaving the legacy/solar interpolator unchanged.
|
|
|
|
|
for other := 0; other < points; other++ {
|
|
|
|
|
if other != point {
|
|
|
|
|
weight *= (tt - nodes[start+other].tt) / (nodes[start+point].tt - nodes[start+other].tt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for coordinate := 0; coordinate < 3; coordinate++ {
|
|
|
|
|
first[coordinate] += weight * nodes[start+point].first[coordinate]
|
|
|
|
|
next[coordinate] += weight * nodes[start+point].next[coordinate]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return first, next, finiteVector3(first) && finiteVector3(next)
|
|
|
|
|
}
|