feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 等时线(食甚/掩甚时刻等值线)的共享常量与工具。
|
||||
// 两个等时线实现(日食、月掩)用同一套容差与时刻取值生成规则,避免口径漂移。
|
||||
|
||||
const (
|
||||
// greatestTimeContourCoverToleranceKM 是"该点是否已被已绘等时线覆盖"的距离门限。
|
||||
// 判据必须用点到折线的距离:同一条曲线被两个种子各画一次时该距离约为 0,而折线弦高在
|
||||
// 1.5° 步长下最大几 km,所以 10 km 既不会漏判重复,也远小于不同支路的间距。
|
||||
greatestTimeContourCoverToleranceKM = 10.0
|
||||
// greatestTimeContourCorrectionBacktracking 是牛顿投影跳出定义域时按二分回退的次数。
|
||||
greatestTimeContourCorrectionBacktracking = 6
|
||||
// greatestTimeContourKMPerDegree 是地面大圆每度的近似长度,用于局部平面投影。
|
||||
greatestTimeContourKMPerDegree = 111.195
|
||||
// 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 JDE2DateByZone(TD2UT(tt, false), time.UTC, false)
|
||||
}
|
||||
|
||||
// greatestTimeContourUTCToTT 把 UTC 时刻换成对应的 TT 儒略日。
|
||||
func greatestTimeContourUTCToTT(value time.Time) float64 {
|
||||
return TD2UT(Date2JDE(value.UTC()), true)
|
||||
}
|
||||
|
||||
// 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 normalizeLongitude(value-startLongitude) * rad * cosine * greatestTimeContourKMPerDegree
|
||||
}
|
||||
toY := func(value float64) float64 { return (value - startLatitude) * greatestTimeContourKMPerDegree }
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user