462 lines
19 KiB
Go
462 lines
19 KiB
Go
|
|
package basic
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"math"
|
|||
|
|
"sort"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
starOccultationLimbDefaultStep = 200 * time.Millisecond
|
|||
|
|
starOccultationLimbMinimumStep = 10 * time.Millisecond
|
|||
|
|
starOccultationLimbMaxSamples = 20000
|
|||
|
|
starOccultationLimbMinSamples = 64
|
|||
|
|
starOccultationLimbBoundStep = 60.0 / 86400.0
|
|||
|
|
starOccultationLimbBoundSpan = 3.5 / 24.0
|
|||
|
|
starOccultationLimbNodeDays = 10.0 / 1440.0
|
|||
|
|
starOccultationLimbNodeSpan = 4.0 / 24.0
|
|||
|
|
starOccultationLimbRootDays = 1e-3 / 86400.0
|
|||
|
|
starOccultationLimbUnrangedPadKM = 1.0
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// StarOccultationLimbProfileOptions 控制精密恒星月掩的星迹采样 / sampling of a precise stellar occultation.
|
|||
|
|
type StarOccultationLimbProfileOptions struct {
|
|||
|
|
// Step 是星迹采样步长;零值 200 ms,小于 10 ms 提升到 10 ms;每段至少 64 个样本、总数不超过 20000,越界时自动调整。短于步长的遮挡或复现可能漏检 / track sampling step.
|
|||
|
|
Step time.Duration
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StarOccultationLimbSample 星迹上一个时刻的月缘几何,角量均为角秒、度 / limb geometry at one instant on the star track.
|
|||
|
|
type StarOccultationLimbSample struct {
|
|||
|
|
Time time.Time
|
|||
|
|
// SeparationArcsec 恒星到站心月心的角距 / star–Moon-centre separation.
|
|||
|
|
SeparationArcsec float64
|
|||
|
|
// LimbRadiusArcsec 恒星方向上的月缘角半径 / limb radius toward the star.
|
|||
|
|
LimbRadiusArcsec float64
|
|||
|
|
// MeanRadiusArcsec 参考圆月面角半径 / circular reference radius.
|
|||
|
|
MeanRadiusArcsec float64
|
|||
|
|
// PositionAngleDeg 恒星相对月心的位置角,自天北向东 / position angle of the star.
|
|||
|
|
PositionAngleDeg float64
|
|||
|
|
// Hidden 恒星位于月缘以内 / star is behind the limb.
|
|||
|
|
Hidden bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// OccultationInterval 一段连续遮挡 / one continuous hidden interval.
|
|||
|
|
type OccultationInterval struct {
|
|||
|
|
Start time.Time
|
|||
|
|
End time.Time
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StarOccultationLimbProfileResult 一次恒星月掩沿星迹的月缘剖面与全部遮挡区间 / limb profile and every hidden interval of a stellar occultation.
|
|||
|
|
type StarOccultationLimbProfileResult struct {
|
|||
|
|
// Occultation 按同一剖面求得的标准结果,掩始、掩终为首次遮挡与最后复现 / standard result on the same profile.
|
|||
|
|
Occultation StarOccultationInfo
|
|||
|
|
// Segments 月缘起伏带内的采样段,按时间排序;深掩时为掩入侧与复现侧两段,掠掩时通常为一段 / samples inside the limb relief band.
|
|||
|
|
Segments [][]StarOccultationLimbSample
|
|||
|
|
// Hidden 全部遮挡区间,按时间排序 / every hidden interval.
|
|||
|
|
Hidden []OccultationInterval
|
|||
|
|
// ReferenceRadiusKM 样本 MeanRadiusArcsec 对应的参考月球半径 / reference lunar radius behind MeanRadiusArcsec.
|
|||
|
|
ReferenceRadiusKM float64
|
|||
|
|
// ReliefMinKM / ReliefMaxKM 判定所用的月缘半径范围 / limb radius range used for gating.
|
|||
|
|
ReliefMinKM float64
|
|||
|
|
ReliefMaxKM float64
|
|||
|
|
// Step 实际采用的采样步长 / effective step.
|
|||
|
|
Step time.Duration
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type starLimbState struct {
|
|||
|
|
separation, limbRadius, meanRadius, positionAngle float64
|
|||
|
|
distanceKM float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// starLimbEphemeris 掩甚前后 ±4 h 的地心月球、恒星视位置与月球本体坐标架插值表;站心换算逐点精确计算。
|
|||
|
|
type starLimbEphemeris struct {
|
|||
|
|
bodies []localEphemerisVectorNode
|
|||
|
|
axes []localEphemerisVectorNode
|
|||
|
|
base lunarLimbFrame
|
|||
|
|
hasDistance bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func newStarLimbEphemeris(greatestTT float64, star StarCoordinate, limb LunarLimb, inst *Instance) (*starLimbEphemeris, bool) {
|
|||
|
|
half := int(math.Round(starOccultationLimbNodeSpan / starOccultationLimbNodeDays))
|
|||
|
|
ephemeris := &starLimbEphemeris{
|
|||
|
|
bodies: make([]localEphemerisVectorNode, 2*half+1),
|
|||
|
|
axes: make([]localEphemerisVectorNode, 2*half+1),
|
|||
|
|
}
|
|||
|
|
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))
|
|||
|
|
starRA, starDec, distanceAU := starApparentRaDecDistanceGeocentric(tt, star, inst)
|
|||
|
|
if distanceAU <= 0 {
|
|||
|
|
distanceAU = 1
|
|||
|
|
} else {
|
|||
|
|
ephemeris.hasDistance = true
|
|||
|
|
}
|
|||
|
|
frame := newLunarLimbFrame(limb, tt, moon, inst)
|
|||
|
|
if !frame.valid || !finiteVector3(moon) {
|
|||
|
|
return nil, false
|
|||
|
|
}
|
|||
|
|
if index == half {
|
|||
|
|
ephemeris.base = frame
|
|||
|
|
}
|
|||
|
|
ephemeris.bodies[index] = localEphemerisVectorNode{tt: tt, first: moon, next: lunarEclipseLimbVector(starRA, starDec, distanceAU)}
|
|||
|
|
ephemeris.axes[index] = localEphemerisVectorNode{tt: tt, first: frame.axisX, next: frame.axisZ}
|
|||
|
|
}
|
|||
|
|
return ephemeris, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (ephemeris *starLimbEphemeris) stateAt(tt float64, observer Observer) (starLimbState, bool) {
|
|||
|
|
moon, starVector, ok := interpolateLocalEphemerisVectors(ephemeris.bodies, tt)
|
|||
|
|
if !ok {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
axisX, axisZ, ok := interpolateLocalEphemerisVectors(ephemeris.axes, tt)
|
|||
|
|
if !ok {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
geoDistance := math.Sqrt(lunarLimbDot(moon, moon))
|
|||
|
|
geoRA := normalizeRA(math.Atan2(moon[1], moon[0]) / rad)
|
|||
|
|
geoDec := math.Asin(clampUnit(moon[2]/geoDistance)) / rad
|
|||
|
|
ut := TT2UTC(tt)
|
|||
|
|
topoRA, topoDec := TopocentricRaDec(geoRA, geoDec, observer.Latitude, observer.Longitude, ut, geoDistance/149597870.7, observer.Height)
|
|||
|
|
topoRA = normalizeRA(topoRA)
|
|||
|
|
topoDistance := topocentricDistanceKM(geoRA, geoDec, geoDistance, observer, ut)
|
|||
|
|
starDistance := math.Sqrt(lunarLimbDot(starVector, starVector))
|
|||
|
|
starRA := normalizeRA(math.Atan2(starVector[1], starVector[0]) / rad)
|
|||
|
|
starDec := math.Asin(clampUnit(starVector[2]/starDistance)) / rad
|
|||
|
|
if ephemeris.hasDistance {
|
|||
|
|
starRA, starDec = TopocentricRaDec(starRA, starDec, observer.Latitude, observer.Longitude, ut, starDistance, observer.Height)
|
|||
|
|
starRA = normalizeRA(starRA)
|
|||
|
|
}
|
|||
|
|
if !finite(topoRA) || !finite(topoDec) || !finite(topoDistance) || topoDistance <= 0 || !finite(starRA) || !finite(starDec) {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
frame := ephemeris.base
|
|||
|
|
if frame.axisX, ok = lunarLimbNormalize(axisX); !ok {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
if frame.axisZ, ok = lunarLimbNormalize(axisZ); !ok {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
// 两轴分别插值后不再严格正交,按 z 轴重新正交化 x 轴。
|
|||
|
|
if frame.axisX, ok = lunarLimbNormalize(lunarLimbReject(frame.axisX, frame.axisZ)); !ok {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
frame.axisY = lunarLimbCross(frame.axisZ, frame.axisX)
|
|||
|
|
radiusKM, ok := frame.radiusKM(lunarEclipseLimbVector(topoRA, topoDec, topoDistance), lunarEclipseLimbVector(starRA, starDec, 1), false)
|
|||
|
|
if !ok {
|
|||
|
|
return starLimbState{}, false
|
|||
|
|
}
|
|||
|
|
return starLimbState{
|
|||
|
|
separation: angularSeparationDegrees(topoRA, topoDec, starRA, starDec) * 3600,
|
|||
|
|
limbRadius: angularSemidiameterArcsec(radiusKM, topoDistance),
|
|||
|
|
meanRadius: angularSemidiameterArcsec(moonEquatorialRadiusKM, topoDistance),
|
|||
|
|
positionAngle: occultationPositionAngle(topoRA, topoDec, starRA, starDec),
|
|||
|
|
distanceKM: topoDistance,
|
|||
|
|
}, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// starOccultationLimbRange 返回剖面半径范围;剖面不报告范围时在掩甚扫一圈并外扩 1 km。
|
|||
|
|
func starOccultationLimbRange(tt float64, star StarCoordinate, observer Observer, limb LunarLimb, 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
|
|||
|
|
}
|
|||
|
|
topoRA, topoDec := moonTopocentricApparentRaDec(tt, observer, -1, inst)
|
|||
|
|
starRA, starDec := starApparentRaDec(tt, star, observer, inst)
|
|||
|
|
view, ok := frame.viewToward(lunarEclipseLimbVector(topoRA, topoDec, 1), lunarEclipseLimbVector(starRA, starDec, 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 - starOccultationLimbUnrangedPadKM, high + starOccultationLimbUnrangedPadKM, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// starOccultationSeparationCrossing 自掩甚向 direction 一侧求角距升到 radiusKM 对应角半径的时刻;角距自掩甚单调增大。
|
|||
|
|
func starOccultationSeparationCrossing(greatestTT float64, direction float64, radiusKM float64, ephemeris *starLimbEphemeris, observer Observer) (float64, bool) {
|
|||
|
|
value := func(tt float64) float64 {
|
|||
|
|
state, ok := ephemeris.stateAt(tt, observer)
|
|||
|
|
if !ok {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
return state.separation - angularSemidiameterArcsec(radiusKM, state.distanceKM)
|
|||
|
|
}
|
|||
|
|
near, nearValue := greatestTT, value(greatestTT)
|
|||
|
|
if !finite(nearValue) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
if nearValue >= 0 {
|
|||
|
|
return greatestTT, true
|
|||
|
|
}
|
|||
|
|
for step := 1; float64(step)*starOccultationLimbBoundStep <= starOccultationLimbBoundSpan; step++ {
|
|||
|
|
far := greatestTT + direction*float64(step)*starOccultationLimbBoundStep
|
|||
|
|
farValue := value(far)
|
|||
|
|
if !finite(farValue) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
if farValue < 0 {
|
|||
|
|
near, nearValue = far, farValue
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
for i := 0; i < 64 && math.Abs(far-near) > starOccultationLimbRootDays; i++ {
|
|||
|
|
mid := (near + far) / 2
|
|||
|
|
if v := value(mid); v < 0 {
|
|||
|
|
near = mid
|
|||
|
|
} else {
|
|||
|
|
far = mid
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return (near + far) / 2, true
|
|||
|
|
}
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type starLimbRoot struct {
|
|||
|
|
tt float64
|
|||
|
|
disappear bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type starLimbScan struct {
|
|||
|
|
segments [][]StarOccultationLimbSample
|
|||
|
|
hidden [][2]float64
|
|||
|
|
lowKM float64
|
|||
|
|
highKM float64
|
|||
|
|
stepDays float64
|
|||
|
|
// grazing 为真表示星迹始终在月缘起伏带内(单段采样)。
|
|||
|
|
grazing bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// starOccultationLimbScanAt 按剖面起伏带把星迹分必见、必掩与带内三段,带外两端遮挡状态由构造保证;ok 为假表示剖面无值或星历失败。
|
|||
|
|
func starOccultationLimbScanAt(greatestTT float64, star StarCoordinate, observer Observer, limb LunarLimb, stepDays float64, location *time.Location, inst *Instance) (starLimbScan, bool) {
|
|||
|
|
lowKM, highKM, ok := starOccultationLimbRange(greatestTT, star, observer, limb, inst)
|
|||
|
|
if !ok {
|
|||
|
|
return starLimbScan{}, false
|
|||
|
|
}
|
|||
|
|
scan := starLimbScan{lowKM: lowKM, highKM: highKM, stepDays: stepDays}
|
|||
|
|
ephemeris, ok := newStarLimbEphemeris(greatestTT, star, limb, inst)
|
|||
|
|
if !ok {
|
|||
|
|
return starLimbScan{}, false
|
|||
|
|
}
|
|||
|
|
outerLeft, okLeft := starOccultationSeparationCrossing(greatestTT, -1, highKM, ephemeris, observer)
|
|||
|
|
outerRight, okRight := starOccultationSeparationCrossing(greatestTT, 1, highKM, ephemeris, observer)
|
|||
|
|
if !okLeft || !okRight {
|
|||
|
|
return starLimbScan{}, false
|
|||
|
|
}
|
|||
|
|
if outerLeft == greatestTT && outerRight == greatestTT {
|
|||
|
|
return scan, true
|
|||
|
|
}
|
|||
|
|
innerLeft, okLeft := starOccultationSeparationCrossing(greatestTT, -1, lowKM, ephemeris, observer)
|
|||
|
|
innerRight, okRight := starOccultationSeparationCrossing(greatestTT, 1, lowKM, ephemeris, observer)
|
|||
|
|
if !okLeft || !okRight {
|
|||
|
|
return starLimbScan{}, false
|
|||
|
|
}
|
|||
|
|
type span struct {
|
|||
|
|
start, end float64
|
|||
|
|
hiddenAtStart bool
|
|||
|
|
hiddenAtEnd bool
|
|||
|
|
forceStart, forceEnd bool
|
|||
|
|
}
|
|||
|
|
spans := []span{{outerLeft, innerLeft, false, true, true, true}, {innerRight, outerRight, true, false, true, true}}
|
|||
|
|
if innerLeft == greatestTT && innerRight == greatestTT {
|
|||
|
|
spans = []span{{outerLeft, outerRight, false, false, true, true}}
|
|||
|
|
}
|
|||
|
|
total, shortest := 0.0, math.Inf(1)
|
|||
|
|
for _, item := range spans {
|
|||
|
|
total += item.end - item.start
|
|||
|
|
shortest = math.Min(shortest, item.end-item.start)
|
|||
|
|
}
|
|||
|
|
// 步长过大时掠掩两端都可见、中间的遮挡会整段漏掉,所以每段至少保留 64 个样本。
|
|||
|
|
if limit := shortest / starOccultationLimbMinSamples; shortest > 0 && stepDays > limit {
|
|||
|
|
stepDays = math.Max(limit, starOccultationLimbMinimumStep.Hours()/24)
|
|||
|
|
}
|
|||
|
|
if total/stepDays > starOccultationLimbMaxSamples {
|
|||
|
|
stepDays = total / starOccultationLimbMaxSamples
|
|||
|
|
}
|
|||
|
|
scan.stepDays = stepDays
|
|||
|
|
scan.grazing = len(spans) == 1
|
|||
|
|
gapAt := func(tt float64) (starLimbState, float64, bool) {
|
|||
|
|
state, ok := ephemeris.stateAt(tt, observer)
|
|||
|
|
return state, state.separation - state.limbRadius, ok
|
|||
|
|
}
|
|||
|
|
var roots []starLimbRoot
|
|||
|
|
for _, item := range spans {
|
|||
|
|
// 剖面范围零宽时带也零长,遮挡状态在该点翻转;不产样本,避免同刻样本。
|
|||
|
|
if item.end-item.start < starOccultationLimbRootDays {
|
|||
|
|
if item.hiddenAtStart != item.hiddenAtEnd {
|
|||
|
|
roots = append(roots, starLimbRoot{tt: item.start, disappear: item.hiddenAtEnd})
|
|||
|
|
}
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
count := int(math.Ceil((item.end - item.start) / stepDays))
|
|||
|
|
if count < 1 {
|
|||
|
|
count = 1
|
|||
|
|
}
|
|||
|
|
samples := make([]StarOccultationLimbSample, 0, count+1)
|
|||
|
|
previousTT, previousHidden := 0.0, false
|
|||
|
|
var previousGap float64
|
|||
|
|
for index := 0; index <= count; index++ {
|
|||
|
|
tt := item.start + (item.end-item.start)*float64(index)/float64(count)
|
|||
|
|
state, gap, ok := gapAt(tt)
|
|||
|
|
if !ok {
|
|||
|
|
return starLimbScan{}, false
|
|||
|
|
}
|
|||
|
|
hidden := gap < 0
|
|||
|
|
// 带两端的遮挡状态由角距保证,消除端点上的舍入抖动。
|
|||
|
|
if index == 0 && item.forceStart {
|
|||
|
|
hidden = item.hiddenAtStart
|
|||
|
|
}
|
|||
|
|
if index == count && item.forceEnd {
|
|||
|
|
hidden = item.hiddenAtEnd
|
|||
|
|
}
|
|||
|
|
samples = append(samples, StarOccultationLimbSample{
|
|||
|
|
Time: occultationTTToLocation(tt, location),
|
|||
|
|
SeparationArcsec: state.separation,
|
|||
|
|
LimbRadiusArcsec: state.limbRadius,
|
|||
|
|
MeanRadiusArcsec: state.meanRadius,
|
|||
|
|
PositionAngleDeg: state.positionAngle,
|
|||
|
|
Hidden: hidden,
|
|||
|
|
})
|
|||
|
|
if index > 0 && hidden != previousHidden {
|
|||
|
|
root := starOccultationLimbBisect(previousTT, tt, previousGap, gap, gapAt)
|
|||
|
|
roots = append(roots, starLimbRoot{tt: root, disappear: hidden})
|
|||
|
|
}
|
|||
|
|
previousTT, previousHidden, previousGap = tt, hidden, gap
|
|||
|
|
}
|
|||
|
|
scan.segments = append(scan.segments, samples)
|
|||
|
|
}
|
|||
|
|
sort.SliceStable(roots, func(i, j int) bool { return roots[i].tt < roots[j].tt })
|
|||
|
|
start := math.NaN()
|
|||
|
|
for _, root := range roots {
|
|||
|
|
if root.disappear {
|
|||
|
|
start = root.tt
|
|||
|
|
} else if !math.IsNaN(start) {
|
|||
|
|
scan.hidden = append(scan.hidden, [2]float64{start, root.tt})
|
|||
|
|
start = math.NaN()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return scan, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// starOccultationLimbBisect 在相邻样本间求月缘间隙零点;端点被强制定号时零点可能落在端点上。
|
|||
|
|
func starOccultationLimbBisect(left, right, leftGap, rightGap float64, gapAt func(float64) (starLimbState, float64, bool)) float64 {
|
|||
|
|
if leftGap*rightGap > 0 {
|
|||
|
|
if math.Abs(leftGap) < math.Abs(rightGap) {
|
|||
|
|
return left
|
|||
|
|
}
|
|||
|
|
return right
|
|||
|
|
}
|
|||
|
|
for i := 0; i < 64 && right-left > starOccultationLimbRootDays; i++ {
|
|||
|
|
mid := (left + right) / 2
|
|||
|
|
_, gap, ok := gapAt(mid)
|
|||
|
|
if !ok {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
if (gap < 0) == (leftGap < 0) {
|
|||
|
|
left, leftGap = mid, gap
|
|||
|
|
} else {
|
|||
|
|
right = mid
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return (left + right) / 2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// starOccultationInfoWithLimb 有月缘时按剖面判定是否成掩,掩始、掩终取首次遮挡与最后复现;剖面无值时退回圆月面。
|
|||
|
|
func starOccultationInfoWithLimb(greatestTT float64, star StarCoordinate, observer Observer, safetyMarginArcsec float64, location *time.Location, limb LunarLimb, inst *Instance) (StarOccultationInfo, bool) {
|
|||
|
|
if limb == nil {
|
|||
|
|
return starOccultationInfoAtGreatest(greatestTT, star, observer, safetyMarginArcsec, location, inst)
|
|||
|
|
}
|
|||
|
|
info, ok := starOccultationGreatestInfo(greatestTT, star, observer, safetyMarginArcsec, location, inst)
|
|||
|
|
if !ok {
|
|||
|
|
return StarOccultationInfo{}, false
|
|||
|
|
}
|
|||
|
|
scan, ok := starOccultationLimbScanAt(greatestTT, star, observer, limb, starOccultationLimbDefaultStep.Hours()/24, location, inst)
|
|||
|
|
if !ok {
|
|||
|
|
return starOccultationInfoAtGreatest(greatestTT, star, observer, safetyMarginArcsec, location, inst)
|
|||
|
|
}
|
|||
|
|
return starOccultationInfoFromScan(info, scan, location), len(scan.hidden) > 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func starOccultationInfoFromScan(info StarOccultationInfo, scan starLimbScan, location *time.Location) StarOccultationInfo {
|
|||
|
|
if len(scan.hidden) == 0 {
|
|||
|
|
info.Type = OccultationNone
|
|||
|
|
info.Immersion, info.Emersion = time.Time{}, time.Time{}
|
|||
|
|
info.ContactsComplete = false
|
|||
|
|
info.LunarLimbApplied = true
|
|||
|
|
return info
|
|||
|
|
}
|
|||
|
|
info.Type = OccultationTotal
|
|||
|
|
if scan.grazing {
|
|||
|
|
info.Type = OccultationGrazing
|
|||
|
|
}
|
|||
|
|
info.Immersion = occultationTTToLocation(scan.hidden[0][0], location)
|
|||
|
|
info.Emersion = occultationTTToLocation(scan.hidden[len(scan.hidden)-1][1], location)
|
|||
|
|
info.ContactsComplete = true
|
|||
|
|
info.LunarLimbApplied = true
|
|||
|
|
return info
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StarOccultationLimbProfileWithInstance 沿星迹按月缘剖面精密求解一次恒星月掩,返回全部遮挡区间与带内采样;info 只取掩甚时刻与观测者,须为 UTC 口径结果(不能传 UT1 标注副本),恒星未被遮挡时 Occultation.Type 为 OccultationNone / precise stellar occultation along the track with a lunar limb profile.
|
|||
|
|
func StarOccultationLimbProfileWithInstance(info StarOccultationInfo, star StarCoordinate, limb LunarLimb, options StarOccultationLimbProfileOptions, inst *Instance) (StarOccultationLimbProfileResult, error) {
|
|||
|
|
if limb == nil {
|
|||
|
|
return StarOccultationLimbProfileResult{}, fmt.Errorf("%w: lunar limb profile is required", ErrInvalidOccultationInput)
|
|||
|
|
}
|
|||
|
|
if err := star.Validate(); err != nil {
|
|||
|
|
return StarOccultationLimbProfileResult{}, err
|
|||
|
|
}
|
|||
|
|
if err := info.Observer.Validate(); err != nil {
|
|||
|
|
return StarOccultationLimbProfileResult{}, err
|
|||
|
|
}
|
|||
|
|
if info.Greatest.IsZero() {
|
|||
|
|
return StarOccultationLimbProfileResult{}, fmt.Errorf("%w: greatest occultation time is required", ErrInvalidOccultationInput)
|
|||
|
|
}
|
|||
|
|
if options.Step < 0 {
|
|||
|
|
return StarOccultationLimbProfileResult{}, fmt.Errorf("%w: profile step cannot be negative", ErrInvalidOccultationInput)
|
|||
|
|
}
|
|||
|
|
step := options.Step
|
|||
|
|
if step == 0 {
|
|||
|
|
step = starOccultationLimbDefaultStep
|
|||
|
|
}
|
|||
|
|
if step < starOccultationLimbMinimumStep {
|
|||
|
|
step = starOccultationLimbMinimumStep
|
|||
|
|
}
|
|||
|
|
location := info.Greatest.Location()
|
|||
|
|
greatestTT := occultationTimeToTT(info.Greatest)
|
|||
|
|
base, ok := starOccultationGreatestInfo(greatestTT, star, info.Observer, math.Inf(1), location, inst)
|
|||
|
|
if !ok {
|
|||
|
|
return StarOccultationLimbProfileResult{}, fmt.Errorf("%w: geometry is unavailable at greatest occultation", ErrInvalidOccultationInput)
|
|||
|
|
}
|
|||
|
|
base.TargetID = star.ID
|
|||
|
|
scan, ok := starOccultationLimbScanAt(greatestTT, star, info.Observer, limb, step.Hours()/24, location, inst)
|
|||
|
|
if !ok {
|
|||
|
|
return StarOccultationLimbProfileResult{}, fmt.Errorf("%w: lunar limb profile has no value along the track", ErrInvalidOccultationInput)
|
|||
|
|
}
|
|||
|
|
profile := StarOccultationLimbProfileResult{
|
|||
|
|
Occultation: starOccultationInfoFromScan(base, scan, location),
|
|||
|
|
Segments: scan.segments,
|
|||
|
|
ReferenceRadiusKM: moonEquatorialRadiusKM,
|
|||
|
|
ReliefMinKM: scan.lowKM,
|
|||
|
|
ReliefMaxKM: scan.highKM,
|
|||
|
|
Step: time.Duration(math.Round(scan.stepDays * 86400 * float64(time.Second))),
|
|||
|
|
}
|
|||
|
|
for _, interval := range scan.hidden {
|
|||
|
|
profile.Hidden = append(profile.Hidden, OccultationInterval{
|
|||
|
|
Start: occultationTTToLocation(interval[0], location),
|
|||
|
|
End: occultationTTToLocation(interval[1], location),
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return profile, nil
|
|||
|
|
}
|