044f2f4381
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
87 lines
3.7 KiB
Go
87 lines
3.7 KiB
Go
package basic
|
||
|
||
// 剖面不报告半径范围时,月掩须在当时视向上扫满一圈求范围,结果与报告范围的同一剖面一致。
|
||
|
||
import (
|
||
"math"
|
||
"testing"
|
||
"time"
|
||
|
||
"b612.me/astro/moon/limb"
|
||
)
|
||
|
||
// unrangedLunarLimb 只暴露 LimbRadiusKM,迫使月掩走整圈扫描。
|
||
type unrangedLunarLimb struct{ profile *limb.Profile }
|
||
|
||
func (u unrangedLunarLimb) LimbRadiusKM(lon, lat, angle float64) (float64, bool) {
|
||
return u.profile.LimbRadiusKM(lon, lat, angle)
|
||
}
|
||
|
||
// limbBruteRangeKM 以剖面步长的 1/8 细扫一圈,作整圈扫描的对拍基准。
|
||
func limbBruteRangeKM(t *testing.T, tt float64, profile LunarLimb, observer Observer, ra, dec float64) (float64, float64) {
|
||
t.Helper()
|
||
geoRA, geoDec := HMoonGeocentricApparentRaDecNWithInstance(tt, -1, nil)
|
||
frame := newLunarLimbFrame(profile, tt, lunarEclipseLimbVector(geoRA, geoDec, HMoonAwayNWithInstance(tt, -1, nil)), nil)
|
||
topoRA, topoDec := moonTopocentricApparentRaDec(tt, observer, -1, nil)
|
||
view, ok := frame.viewToward(lunarEclipseLimbVector(topoRA, topoDec, 1), lunarEclipseLimbVector(ra, dec, 1))
|
||
if !frame.valid || !ok {
|
||
t.Fatal("limb view failed")
|
||
}
|
||
low, high := math.Inf(1), math.Inf(-1)
|
||
for offset := 0.0; offset < 360; offset += frame.stepDeg / 8 {
|
||
radius, ok := frame.radiusAt(view, offset)
|
||
if !ok {
|
||
t.Fatal("limb radius failed")
|
||
}
|
||
low, high = math.Min(low, radius), math.Max(high, radius)
|
||
}
|
||
return low, high
|
||
}
|
||
|
||
func TestOccultationLimbRangeScansFullCircleWithoutProvider(t *testing.T) {
|
||
profile := limb.Default()
|
||
unranged := unrangedLunarLimb{profile: profile}
|
||
observer := Observer{Longitude: 41.5907, Latitude: -55.07036}
|
||
tt := occultationTimeToTT(time.Date(2026, 2, 11, 3, 47, 15, 0, time.UTC))
|
||
ra, dec := starApparentRaDec(tt, limbTestAntares, observer, nil)
|
||
wantLow, wantHigh := limbBruteRangeKM(t, tt, unranged, observer, ra, dec)
|
||
// 剖面网格节点即极值,整圈扫描与细扫只差插值误差;扫描步长放粗会漏掉极值。
|
||
const tolerance = 0.05
|
||
starLow, starHigh, ok := starOccultationLimbRange(tt, limbTestAntares, observer, unranged, nil)
|
||
if !ok {
|
||
t.Fatal("star range failed")
|
||
}
|
||
target := func(float64) (float64, float64, float64, float64, bool) { return ra, dec, 0, 0, true }
|
||
low, high, ok := occultationLimbRangeKM(tt, unranged, observer, target, nil)
|
||
if !ok {
|
||
t.Fatal("occultation range failed")
|
||
}
|
||
for _, got := range []struct {
|
||
name string
|
||
low, high float64
|
||
pad float64
|
||
}{{"star", starLow, starHigh, starOccultationLimbUnrangedPadKM}, {"planet", low, high, occultationLimbUnrangedPadKM}} {
|
||
if math.Abs(got.low+got.pad-wantLow) > tolerance || math.Abs(got.high-got.pad-wantHigh) > tolerance {
|
||
t.Errorf("%s: range %.3f–%.3f km, brute force %.3f–%.3f km", got.name, got.low+got.pad, got.high-got.pad, wantLow, wantHigh)
|
||
}
|
||
}
|
||
rangedLow, rangedHigh := profile.RadiusRangeKM()
|
||
if wantLow < rangedLow || wantHigh > rangedHigh || wantHigh-wantLow < 1 {
|
||
t.Errorf("brute force %.3f–%.3f km outside table range %.3f–%.3f km or too flat", wantLow, wantHigh, rangedLow, rangedHigh)
|
||
}
|
||
}
|
||
|
||
func TestStarOccultationUnrangedLimbMatchesRanged(t *testing.T) {
|
||
const lon, lat = 41.5907, -55.07036 + 0.4/111.2
|
||
ranged := limbTestAntaresEvent(t, lon, lat, limb.Default())
|
||
unranged := limbTestAntaresEvent(t, lon, lat, unrangedLunarLimb{profile: limb.Default()})
|
||
if len(ranged) != 1 || len(unranged) != 1 || ranged[0].Type != OccultationGrazing || unranged[0].Type != ranged[0].Type {
|
||
t.Fatalf("ranged %+v vs unranged %+v, want the same limb graze", ranged, unranged)
|
||
}
|
||
for _, pair := range [][2]time.Time{{ranged[0].Immersion, unranged[0].Immersion}, {ranged[0].Emersion, unranged[0].Emersion}} {
|
||
if d := math.Abs(pair[0].Sub(pair[1]).Seconds()); d > 0.05 {
|
||
t.Errorf("unranged limb moved a contact by %.3f s", d)
|
||
}
|
||
}
|
||
}
|