Files
astro/basic/occultation_limb.go
T

361 lines
15 KiB
Go
Raw Normal View History

package basic
import (
"math"
"time"
)
// occultationLimbTarget 给出某时刻目标的地心视赤经、赤纬(度)与地心距(km,恒星为 0)及站心角半径(角秒)。
type occultationLimbTarget func(tt float64) (ra, dec, distanceKM, radiusArcsec float64, ok bool)
func planetOccultationLimbTarget(config planetOccultationConfig, observer Observer) occultationLimbTarget {
return func(tt float64) (float64, float64, float64, float64, bool) {
ra, dec := config.apparentRaDecN(tt, -1)
distanceKM := config.earthDistanceN(tt, -1) * angularDiameterAstronomicalUnitKM
radius := planetTopocentricSemidiameterN(tt, config, observer, -1)
return ra, dec, distanceKM, radius, finite(ra) && finite(dec) && finite(distanceKM) && finite(radius)
}
}
const (
occultationLimbCrossingStepDays = 60.0 / 86400.0
occultationLimbCrossingSpanDays = 3.5 / 24.0
occultationLimbRootDays = 1e-3 / 86400.0
occultationLimbMinSamples = 64
occultationLimbMaxSamples = 4000
occultationLimbBandPadArcsec = 0.05
occultationLimbUnrangedPadKM = 1.0
planetOccultationLimbStepDays = 0.25 / 86400.0
)
// occultationLimbRangeKM 返回剖面半径范围;剖面不报告范围时在 tt 时刻朝目标扫一圈并外扩 1 km。
func occultationLimbRangeKM(tt float64, limb LunarLimb, observer Observer, target occultationLimbTarget, inst *Instance) (float64, float64, bool) {
geoRA, geoDec := HMoonGeocentricApparentRaDecNWithInstance(tt, -1, inst)
geoDistance := HMoonAwayNWithInstance(tt, -1, inst)
frame := newLunarLimbFrame(limb, tt, lunarEclipseLimbVector(geoRA, geoDec, geoDistance), inst)
if !frame.valid {
return 0, 0, false
}
if frame.ranged {
return frame.minKM, frame.maxKM, true
}
ra, dec, _, _, ok := target(tt)
if !ok {
return 0, 0, false
}
topoRA, topoDec := moonTopocentricApparentRaDec(tt, observer, -1, inst)
view, ok := frame.viewToward(lunarEclipseLimbVector(topoRA, topoDec, 1), lunarEclipseLimbVector(ra, dec, 1))
if !ok {
return 0, 0, false
}
low, high := math.Inf(1), math.Inf(-1)
for offset := 0.0; offset < 360; offset += frame.stepDeg {
radius, ok := frame.radiusAt(view, offset)
if !ok {
return 0, 0, false
}
low, high = math.Min(low, radius), math.Max(high, radius)
}
return low - occultationLimbUnrangedPadKM, high + occultationLimbUnrangedPadKM, true
}
// occultationLimbCrossing 自 greatestTT 向 direction 一侧求圆月面间隙升到 level 的时刻;间隙自掩甚单调增大,起点已不低于 level 时返回起点。
func occultationLimbCrossing(greatestTT, direction, level float64, circular func(float64) (float64, bool)) (float64, bool) {
near := greatestTT
value, ok := circular(near)
if !ok {
return 0, false
}
if value >= level {
return greatestTT, true
}
for step := 1; float64(step)*occultationLimbCrossingStepDays <= occultationLimbCrossingSpanDays; step++ {
far := greatestTT + direction*float64(step)*occultationLimbCrossingStepDays
farValue, ok := circular(far)
if !ok {
return 0, false
}
if farValue < level {
near = far
continue
}
for i := 0; i < 64 && math.Abs(far-near) > occultationLimbRootDays; i++ {
mid := (near + far) / 2
midValue, ok := circular(mid)
if !ok {
return 0, false
}
if midValue < level {
near = mid
} else {
far = mid
}
}
return (near + far) / 2, true
}
return 0, false
}
// occultationLimbOuterRoots 在 [start, end] 上采样月缘间隙,返回首个由正转负与最后一个由负转正的零点;
// 端点状态由调用方保证(hiddenAtStart、hiddenAtEnd),ok 为假表示剖面无值或星历失败。
func occultationLimbOuterRoots(start, end float64, hiddenAtStart, hiddenAtEnd bool, gap func(float64) (float64, bool)) (first, last float64, found, ok bool) {
if end <= start {
return 0, 0, false, true
}
count := int(math.Ceil((end - start) / planetOccultationLimbStepDays))
if count < occultationLimbMinSamples {
count = occultationLimbMinSamples
}
if count > occultationLimbMaxSamples {
count = occultationLimbMaxSamples
}
first, last = math.NaN(), math.NaN()
previousTT, previousHidden := start, hiddenAtStart
for index := 0; index <= count; index++ {
tt := start + (end-start)*float64(index)/float64(count)
value, valueOK := gap(tt)
if !valueOK {
return 0, 0, false, false
}
hidden := value < 0
if index == 0 {
hidden = hiddenAtStart
} else if index == count {
hidden = hiddenAtEnd
}
if index > 0 && hidden != previousHidden {
root := occultationLimbBisect(previousTT, tt, previousHidden, gap)
if hidden && math.IsNaN(first) {
first = root
}
if !hidden {
last = root
}
}
previousTT, previousHidden = tt, hidden
}
return first, last, !math.IsNaN(first) || !math.IsNaN(last), true
}
// occultationLimbBisect 在相邻样本间求遮挡状态翻转的时刻;leftHidden 是左端的遮挡状态。
func occultationLimbBisect(left, right float64, leftHidden bool, gap func(float64) (float64, bool)) float64 {
for i := 0; i < 64 && right-left > occultationLimbRootDays; i++ {
mid := (left + right) / 2
value, ok := gap(mid)
if !ok {
break
}
if (value < 0) == leftHidden {
left = mid
} else {
right = mid
}
}
return (left + right) / 2
}
// planetOccultationInfoWithLimb 有月缘时按剖面判定行星月掩:只在圆月面间隙落进月缘起伏带的时段采样全方向月缘间隙,
// 取最外侧的外切与内切;月缘可新增或取消圆月面的掠掩与全掩,有正持续时间的接触一律按偏掩或全掩上报。剖面无值时退回圆月面结果。
func planetOccultationInfoWithLimb(greatestTT float64, config planetOccultationConfig, observer Observer, safetyMarginArcsec float64, location *time.Location, limb LunarLimb, inst *Instance) (PlanetOccultationInfo, bool) {
circularInfo, circularOK := planetOccultationInfoAtGreatest(greatestTT, config, observer, safetyMarginArcsec, location, inst)
if limb == nil {
return circularInfo, circularOK
}
info, state, ok := planetOccultationGreatestInfo(greatestTT, config, observer, safetyMarginArcsec, location, inst)
if !ok {
return circularInfo, circularOK
}
target := planetOccultationLimbTarget(config, observer)
lowKM, highKM, ok := occultationLimbRangeKM(greatestTT, limb, observer, target, inst)
if !ok {
return circularInfo, circularOK
}
ephemeris, ok := newPlanetLimbEphemeris(greatestTT, config, limb, inst)
if !ok {
return circularInfo, circularOK
}
// 月缘间隙 ≈ 圆月面间隙 −(月缘半径 − 参考半径),所以圆月面间隙不低于 bandHigh 必不接触、不高于 bandLow 必已接触。
// 起伏带以角秒计,外扩一点容差覆盖月心距在事件内的变化。
scale := state.moonSemidiameter / moonEquatorialRadiusKM
bandLow := (lowKM-moonEquatorialRadiusKM)*scale - occultationLimbBandPadArcsec
bandHigh := (highKM-moonEquatorialRadiusKM)*scale + occultationLimbBandPadArcsec
circular := func(internal bool) func(float64) (float64, bool) {
return func(tt float64) (float64, bool) { return ephemeris.circularGap(tt, observer, internal) }
}
limbGap := func(internal bool) func(float64) (float64, bool) {
return func(tt float64) (float64, bool) { return ephemeris.limbGap(tt, observer, internal) }
}
// solve 求一种接触的最外侧掩入、复现:圆月面间隙在 [bandLow, bandHigh] 之外的两端遮挡状态已知,只在带内采样。
solve := func(internal bool, circularMinimum float64) (float64, float64, bool, bool) {
if circularMinimum >= bandHigh {
return 0, 0, false, true
}
outerLeft, okLeft := occultationLimbCrossing(greatestTT, -1, bandHigh, circular(internal))
outerRight, okRight := occultationLimbCrossing(greatestTT, 1, bandHigh, circular(internal))
if !okLeft || !okRight {
return 0, 0, false, false
}
if circularMinimum > bandLow {
first, last, found, ok := occultationLimbOuterRoots(outerLeft, outerRight, false, false, limbGap(internal))
return first, last, found && !math.IsNaN(first) && !math.IsNaN(last), ok
}
innerLeft, okLeft := occultationLimbCrossing(greatestTT, -1, bandLow, circular(internal))
innerRight, okRight := occultationLimbCrossing(greatestTT, 1, bandLow, circular(internal))
if !okLeft || !okRight {
return 0, 0, false, false
}
first, _, foundLeft, okLeftRoots := occultationLimbOuterRoots(outerLeft, innerLeft, false, true, limbGap(internal))
_, last, foundRight, okRightRoots := occultationLimbOuterRoots(innerRight, outerRight, true, false, limbGap(internal))
return first, last, foundLeft && foundRight && !math.IsNaN(first) && !math.IsNaN(last), okLeftRoots && okRightRoots
}
externalImmersion, externalEmersion, external, ok := solve(false, state.externalContactMetric)
if !ok {
return circularInfo, circularOK
}
if !external {
return PlanetOccultationInfo{}, false
}
internalImmersion, internalEmersion, internal, ok := solve(true, state.internalContactMetric)
if !ok {
return circularInfo, circularOK
}
info.Type = OccultationPartial
info.ExternalImmersion = occultationTTToLocation(externalImmersion, location)
info.ExternalEmersion = occultationTTToLocation(externalEmersion, location)
if internal && internalImmersion > externalImmersion && internalEmersion < externalEmersion && internalImmersion < internalEmersion {
info.Type = OccultationTotal
info.HasInternalContacts = true
info.InternalImmersion = occultationTTToLocation(internalImmersion, location)
info.InternalEmersion = occultationTTToLocation(internalEmersion, location)
}
info.ContactsComplete = true
info.LunarLimbApplied = true
return info, true
}
// planetLimbEphemeris 掩甚前后 ±4 h 的地心月球、行星视位置与月球本体坐标架插值表;站心换算逐点精确计算。
type planetLimbEphemeris struct {
bodies []localEphemerisVectorNode
axes []localEphemerisVectorNode
base lunarLimbFrame
radiusKM float64
}
func newPlanetLimbEphemeris(greatestTT float64, config planetOccultationConfig, limb LunarLimb, inst *Instance) (*planetLimbEphemeris, bool) {
half := int(math.Round(starOccultationLimbNodeSpan / starOccultationLimbNodeDays))
ephemeris := &planetLimbEphemeris{
bodies: make([]localEphemerisVectorNode, 2*half+1),
axes: make([]localEphemerisVectorNode, 2*half+1),
radiusKM: config.equatorialRadiusKM,
}
for index := range ephemeris.bodies {
tt := greatestTT + float64(index-half)*starOccultationLimbNodeDays
moonRA, moonDec := HMoonGeocentricApparentRaDecNWithInstance(tt, -1, inst)
moon := lunarEclipseLimbVector(moonRA, moonDec, HMoonAwayNWithInstance(tt, -1, inst))
planetRA, planetDec := config.apparentRaDecN(tt, -1)
planet := lunarEclipseLimbVector(planetRA, planetDec, config.earthDistanceN(tt, -1)*angularDiameterAstronomicalUnitKM)
frame := newLunarLimbFrame(limb, tt, moon, inst)
if !frame.valid || !finiteVector3(moon) || !finiteVector3(planet) {
return nil, false
}
if index == half {
ephemeris.base = frame
}
ephemeris.bodies[index] = localEphemerisVectorNode{tt: tt, first: moon, next: planet}
ephemeris.axes[index] = localEphemerisVectorNode{tt: tt, first: frame.axisX, next: frame.axisZ}
}
return ephemeris, true
}
// planetLimbTopocentric 是插值得到的站心月球、行星方向与距离(km),以及该时刻的月球本体坐标架。
type planetLimbTopocentric struct {
moon, planet [3]float64
moonDistance, planetRadius float64
frame lunarLimbFrame
}
func (ephemeris *planetLimbEphemeris) topocentricAt(tt float64, observer Observer) (planetLimbTopocentric, bool) {
moon, planet, ok := interpolateLocalEphemerisVectors(ephemeris.bodies, tt)
if !ok {
return planetLimbTopocentric{}, false
}
axisX, axisZ, ok := interpolateLocalEphemerisVectors(ephemeris.axes, tt)
if !ok {
return planetLimbTopocentric{}, false
}
ut := TT2UTC(tt)
toTopocentric := func(vector [3]float64) (float64, float64, float64, bool) {
distance := math.Sqrt(lunarLimbDot(vector, vector))
ra := normalizeRA(math.Atan2(vector[1], vector[0]) / rad)
dec := math.Asin(clampUnit(vector[2]/distance)) / rad
topoRA, topoDec := TopocentricRaDec(ra, dec, observer.Latitude, observer.Longitude, ut, distance/angularDiameterAstronomicalUnitKM, observer.Height)
topoDistance := topocentricDistanceKM(ra, dec, distance, observer, ut)
return normalizeRA(topoRA), topoDec, topoDistance, finite(topoRA) && finite(topoDec) && finite(topoDistance) && topoDistance > 0
}
moonRA, moonDec, moonDistance, okMoon := toTopocentric(moon)
planetRA, planetDec, planetDistance, okPlanet := toTopocentric(planet)
if !okMoon || !okPlanet || planetDistance <= ephemeris.radiusKM {
return planetLimbTopocentric{}, false
}
frame := ephemeris.base
if frame.axisZ, ok = lunarLimbNormalize(axisZ); !ok {
return planetLimbTopocentric{}, false
}
if frame.axisX, ok = lunarLimbNormalize(lunarLimbReject(axisX, frame.axisZ)); !ok {
return planetLimbTopocentric{}, false
}
frame.axisY = lunarLimbCross(frame.axisZ, frame.axisX)
return planetLimbTopocentric{
moon: lunarEclipseLimbVector(moonRA, moonDec, moonDistance),
planet: lunarEclipseLimbVector(planetRA, planetDec, 1),
moonDistance: moonDistance,
planetRadius: math.Asin(ephemeris.radiusKM / planetDistance),
frame: frame,
}, true
}
// circularGap 圆月面外切或内切间隙(角秒),与 planetOccultationStateAt 同口径。
func (ephemeris *planetLimbEphemeris) circularGap(tt float64, observer Observer, internal bool) (float64, bool) {
state, ok := ephemeris.topocentricAt(tt, observer)
if !ok {
return 0, false
}
separation := math.Acos(clampUnit(lunarLimbDot(state.moon, state.planet) / state.moonDistance))
moonRadius := math.Asin(moonEquatorialRadiusKM / state.moonDistance)
if internal {
return (separation - moonRadius + state.planetRadius) * angularDiameterArcsecPerRadian, true
}
return (separation - moonRadius - state.planetRadius) * angularDiameterArcsecPerRadian, true
}
// limbGap 全方向月缘外切或内切间隙(角秒)。
func (ephemeris *planetLimbEphemeris) limbGap(tt float64, observer Observer, internal bool) (float64, bool) {
state, ok := ephemeris.topocentricAt(tt, observer)
if !ok {
return 0, false
}
view, ok := state.frame.viewToward(state.moon, state.planet)
if !ok {
return 0, false
}
contact := lunarLimbContact{
frame: &state.frame,
view: view,
distanceKM: state.moonDistance,
separation: math.Acos(clampUnit(lunarLimbDot(state.moon, state.planet) / state.moonDistance)),
targetRadius: state.planetRadius,
}
if internal {
inner, ok := contact.innerRadius(true)
if !ok {
return 0, false
}
return (contact.separation - math.Abs(inner-contact.targetRadius)) * angularDiameterArcsecPerRadian, true
}
outer, ok := contact.outerRadius()
if !ok {
return 0, false
}
return (contact.separation - outer - contact.targetRadius) * angularDiameterArcsecPerRadian, true
}