feat: 引入可切换星历模型并扩展天象观测链路

- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算
- 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询
- 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出
- 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解
- 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤
- 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持
- 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理
- 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
2026-10-15 23:54:15 +08:00
parent 16c62a97d5
commit 044f2f4381
674 changed files with 62983 additions and 8396 deletions
+7 -19
View File
@@ -5,7 +5,7 @@ import (
"math"
"time"
"b612.me/astro"
"b612.me/astro/basic"
"b612.me/astro/internal/geodata"
"b612.me/astro/internal/occultationgeo"
"b612.me/astro/moon"
@@ -124,7 +124,7 @@ func marshalStarOccultation(path moon.StarOccultationPath, markerOptions *TimeMa
if scaleErr != nil {
return nil, scaleErr
}
if timeScale == astro.TimeScaleUT1 {
if timeScale == basic.TimeScaleUT1 {
path = moon.StarOccultationPathInUT1(path)
}
if err := validateStarOccultationPathData(path); err != nil {
@@ -269,7 +269,7 @@ func marshalPlanetOccultation(path moon.PlanetOccultationPath, markerOptions *Ti
if scaleErr != nil {
return nil, scaleErr
}
if timeScale == astro.TimeScaleUT1 {
if timeScale == basic.TimeScaleUT1 {
path = moon.PlanetOccultationPathInUT1(path)
}
if err := path.Planet.Validate(); err != nil {
@@ -846,7 +846,7 @@ func occultationNearestRingVertex(
index := -1
distance := math.Inf(1)
for candidate, value := range ring {
current := occultationGeoDistanceKM(point, value)
current := geodata.EquatorialDistanceKM(point, value)
if current < distance {
index, distance = candidate, current
}
@@ -862,7 +862,7 @@ func occultationRingPathLengthKM(ring []geodata.GeoPoint, start, end, direction
index := start
for index != end {
next := (index + direction + len(ring)) % len(ring)
length += occultationGeoDistanceKM(ring[index], ring[next])
length += geodata.EquatorialDistanceKM(ring[index], ring[next])
index = next
if length > 1e8 {
return math.Inf(1)
@@ -873,7 +873,7 @@ func occultationRingPathLengthKM(ring []geodata.GeoPoint, start, end, direction
func occultationRingEdgesWithinKM(ring []geodata.GeoPoint, maximum float64) bool {
for index := 1; index < len(ring); index++ {
if occultationGeoDistanceKM(ring[index-1], ring[index]) > maximum {
if geodata.EquatorialDistanceKM(ring[index-1], ring[index]) > maximum {
return false
}
}
@@ -883,23 +883,11 @@ func occultationRingEdgesWithinKM(ring []geodata.GeoPoint, maximum float64) bool
func occultationGeoLineLengthKM(points []geodata.GeoPoint) float64 {
length := 0.0
for index := 1; index < len(points); index++ {
length += occultationGeoDistanceKM(points[index-1], points[index])
length += geodata.EquatorialDistanceKM(points[index-1], points[index])
}
return length
}
func occultationGeoDistanceKM(first, second geodata.GeoPoint) float64 {
const radiusKM = 6378.1366
firstLatitude := first.Latitude * math.Pi / 180
secondLatitude := second.Latitude * math.Pi / 180
deltaLatitude := secondLatitude - firstLatitude
deltaLongitude := math.Remainder(second.Longitude-first.Longitude, 360) * math.Pi / 180
sineLatitude := math.Sin(deltaLatitude / 2)
sineLongitude := math.Sin(deltaLongitude / 2)
a := sineLatitude*sineLatitude + math.Cos(firstLatitude)*math.Cos(secondLatitude)*sineLongitude*sineLongitude
return 2 * radiusKM * math.Atan2(math.Sqrt(math.Max(0, a)), math.Sqrt(math.Max(0, 1-a)))
}
func reverseOccultationGeoPoints(points []geodata.GeoPoint) {
for left, right := 0, len(points)-1; left < right; left, right = left+1, right-1 {
points[left], points[right] = points[right], points[left]