044f2f4381
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
127 lines
5.3 KiB
Go
127 lines
5.3 KiB
Go
package basic
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"b612.me/astro/internal/geodata"
|
|
)
|
|
|
|
// 等时线(食甚/掩甚时刻等值线)的共享常量与工具。
|
|
// 两个等时线实现(日食、月掩)用同一套容差与时刻取值生成规则,避免口径漂移。
|
|
|
|
const (
|
|
// greatestTimeContourCoverToleranceKM 是"该点是否已被已绘等时线覆盖"的距离门限。
|
|
// 判据必须用点到折线的距离:同一条曲线被两个种子各画一次时该距离约为 0,而折线弦高在
|
|
// 1.5° 步长下最大几 km,所以 10 km 既不会漏判重复,也远小于不同支路的间距。
|
|
greatestTimeContourCoverToleranceKM = 10.0
|
|
// greatestTimeContourCorrectionBacktracking 是牛顿投影跳出定义域时按二分回退的次数。
|
|
greatestTimeContourCorrectionBacktracking = 6
|
|
// greatestTimeContourMaxLevels 是单个事件允许的等时线条数上限;超出部分按时间截断,
|
|
// 避免极长事件请求出上万条曲线。
|
|
greatestTimeContourMaxLevels = 64
|
|
// greatestTimeContourResidualTolerance 是残差零点判定的收敛容差。
|
|
greatestTimeContourResidualTolerance = 1e-9
|
|
// greatestTimeContourBisectionIterations 是二分求根的迭代上限。
|
|
greatestTimeContourBisectionIterations = 64
|
|
)
|
|
|
|
// greatestTimeContourDifference 优先用中心差分,一侧越界时退化为单侧差分。
|
|
func greatestTimeContourDifference(center, positive, negative, step float64, positiveOK, negativeOK bool) (float64, bool) {
|
|
switch {
|
|
case positiveOK && negativeOK:
|
|
return (positive - negative) / (2 * step), true
|
|
case positiveOK:
|
|
return (positive - center) / step, true
|
|
case negativeOK:
|
|
return (center - negative) / step, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// greatestTimeContourBisect 在 [left,right] 上二分求残差零点;residual 在定义域外返回 NaN。
|
|
func greatestTimeContourBisect(
|
|
residual func(longitude, latitude float64) float64,
|
|
left, right, latitude, leftValue float64,
|
|
) (float64, bool) {
|
|
for iteration := 0; iteration < greatestTimeContourBisectionIterations; iteration++ {
|
|
middle := (left + right) / 2
|
|
middleValue := residual(middle, latitude)
|
|
if !finite(middleValue) {
|
|
return 0, false
|
|
}
|
|
if math.Abs(middleValue) <= greatestTimeContourResidualTolerance || right-left <= 1e-9 {
|
|
return middle, true
|
|
}
|
|
if leftValue*middleValue <= 0 {
|
|
right = middle
|
|
} else {
|
|
left, leftValue = middle, middleValue
|
|
}
|
|
}
|
|
return (left + right) / 2, true
|
|
}
|
|
|
|
// greatestTimeContourTickTolerance 是对齐网格在窗口边界上的容差,远小于任何合法步长。
|
|
const greatestTimeContourTickTolerance = time.Millisecond
|
|
|
|
// greatestTimeContourLevel 是一条等时线对应的时刻取值:TT 儒略日与它对应的时刻必须成对传递,
|
|
// 前者用于求根,后者用于标注(按步长生成时它是原始对齐时刻,避免 JDE 往返把整分截断成前一分钟)。
|
|
type greatestTimeContourLevel struct {
|
|
tt float64
|
|
at time.Time
|
|
}
|
|
|
|
// greatestTimeContourAlignedLevels 生成覆盖 [startTT,endTT] 且对齐到 step 整刻度的时刻取值,
|
|
// 最多 maxLevels 条;起点或终点恰好落在整刻度上时该刻度只生成一次。
|
|
func greatestTimeContourAlignedLevels(startTT, endTT float64, step time.Duration, maxLevels int) []greatestTimeContourLevel {
|
|
if step <= 0 || maxLevels <= 0 || endTT <= startTT {
|
|
return nil
|
|
}
|
|
start := greatestTimeContourTTToUTC(startTT)
|
|
first := start.Truncate(step)
|
|
// TT 儒略日往返有约 40 µs(1 ULP)误差,恰好落在整刻度上的窗口边界会算到刻度外一点点,容差按 1 ms 给。
|
|
if first.Add(greatestTimeContourTickTolerance).Before(start) {
|
|
first = first.Add(step)
|
|
}
|
|
end := greatestTimeContourTTToUTC(endTT)
|
|
levels := make([]greatestTimeContourLevel, 0, maxLevels)
|
|
for current := first; !current.Add(-greatestTimeContourTickTolerance).After(end) && len(levels) < maxLevels; current = current.Add(step) {
|
|
levels = append(levels, greatestTimeContourLevel{tt: greatestTimeContourUTCToTT(current), at: current})
|
|
}
|
|
return levels
|
|
}
|
|
|
|
// greatestTimeContourTTToUTC 把 TT 儒略日换成对应的 UTC 时刻。
|
|
func greatestTimeContourTTToUTC(tt float64) time.Time {
|
|
return JD2DateByZone(TT2UTC(tt), time.UTC, false)
|
|
}
|
|
|
|
// greatestTimeContourUTCToTT 把 UTC 时刻换成对应的 TT 儒略日。
|
|
func greatestTimeContourUTCToTT(value time.Time) float64 {
|
|
return UTC2TT(Date2JD(value.UTC()))
|
|
}
|
|
|
|
// greatestTimeContourPointSegmentKM 返回点到折线段的距离,单位 km。
|
|
// 段长只有 1–2 度,用局部等距圆柱近似即可,误差远小于覆盖容差。
|
|
func greatestTimeContourPointSegmentKM(longitude, latitude, startLongitude, startLatitude, endLongitude, endLatitude float64) float64 {
|
|
cosine := math.Cos(latitude * rad)
|
|
toX := func(value float64) float64 {
|
|
return geodata.NormalizeLongitude(value-startLongitude) * rad * cosine * geodata.EarthKMPerDegree
|
|
}
|
|
toY := func(value float64) float64 { return (value - startLatitude) * geodata.EarthKMPerDegree }
|
|
pointX, pointY := toX(longitude), toY(latitude)
|
|
endX, endY := toX(endLongitude), toY(endLatitude)
|
|
lengthSquared := endX*endX + endY*endY
|
|
projection := 0.0
|
|
if lengthSquared > 0 {
|
|
projection = (pointX*endX + pointY*endY) / lengthSquared
|
|
if projection < 0 {
|
|
projection = 0
|
|
} else if projection > 1 {
|
|
projection = 1
|
|
}
|
|
}
|
|
return math.Hypot(pointX-projection*endX, pointY-projection*endY)
|
|
}
|