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
+6 -22
View File
@@ -80,7 +80,7 @@ func prepareTopologyPoints(points []GeoPoint) ([]GeoPoint, bool) {
normalized := make([]GeoPoint, len(points))
copy(normalized, points)
for index := range normalized {
normalized[index].Longitude = normalizeLongitude(normalized[index].Longitude)
normalized[index].Longitude = NormalizeLongitude(normalized[index].Longitude)
}
return normalized, true
}
@@ -382,17 +382,17 @@ func hemisphereIntersection(a, b GeoPoint) GeoPoint {
firstSign := firstVector.z
secondSign := secondVector.z
if math.Abs(firstSign) <= 1e-15 {
return GeoPoint{Longitude: normalizeLongitude(first.Longitude), Latitude: 0}
return GeoPoint{Longitude: NormalizeLongitude(first.Longitude), Latitude: 0}
}
if math.Abs(secondSign) <= 1e-15 {
return GeoPoint{Longitude: normalizeLongitude(second.Longitude), Latitude: 0}
return GeoPoint{Longitude: NormalizeLongitude(second.Longitude), Latitude: 0}
}
left, right := 0.0, 1.0
for iteration := 0; iteration < 64; iteration++ {
middle := (left + right) / 2
point := sphericalInterpolate(first, second, middle)
if point.Latitude == 0 || right-left <= 1e-13 {
return GeoPoint{Longitude: normalizeLongitude(point.Longitude), Latitude: 0}
return GeoPoint{Longitude: NormalizeLongitude(point.Longitude), Latitude: 0}
}
if point.Latitude*first.Latitude > 0 {
left = middle
@@ -401,7 +401,7 @@ func hemisphereIntersection(a, b GeoPoint) GeoPoint {
}
}
point := sphericalInterpolate(first, second, (left+right)/2)
return GeoPoint{Longitude: normalizeLongitude(point.Longitude), Latitude: 0}
return GeoPoint{Longitude: NormalizeLongitude(point.Longitude), Latitude: 0}
}
func sphericalLongitudeIntersection(a, b GeoPoint, boundary, adjustedLongitude float64) GeoPoint {
@@ -434,14 +434,6 @@ func sphericalLongitudeIntersection(a, b GeoPoint, boundary, adjustedLongitude f
return GeoPoint{Longitude: boundary, Latitude: point.Latitude}
}
func normalizeLongitude(value float64) float64 {
value = math.Mod(value+180, 360)
if value < 0 {
value += 360
}
return value - 180
}
// equirectangularSeamShift 返回把等经纬接缝从 ±180 搬到视图中心对面所需的经度旋转量。
// 视图中心为 0 时返回 0,调用方即可沿用未旋转的原有路径,保证既有输出逐字节不变。
func equirectangularSeamShift(view ClipView) float64 {
@@ -473,7 +465,7 @@ func rotateLongitudes(points []GeoPoint, shift float64) []GeoPoint {
rotated := make([]GeoPoint, len(points))
copy(rotated, points)
for index := range rotated {
rotated[index].Longitude = normalizeLongitude180(rotated[index].Longitude + shift)
rotated[index].Longitude = NormalizeLongitude(rotated[index].Longitude + shift)
}
return rotated
}
@@ -493,11 +485,3 @@ func unshiftLongitudesInPlace(points []GeoPoint, shift float64) {
points[index].Longitude = value
}
}
func normalizeLongitude180(longitude float64) float64 {
value := math.Mod(longitude+180, 360)
if value < 0 {
value += 360
}
return value - 180
}