044f2f4381
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
99 lines
3.7 KiB
Go
99 lines
3.7 KiB
Go
package occultationgeo
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"b612.me/astro/basic"
|
|
"b612.me/astro/internal/geodata"
|
|
)
|
|
|
|
const defaultRiseSetProjectedSpacingKM = 35.0
|
|
|
|
// DensifyRiseSetCurves 按投影距离插入大地线样本,且不改变端点和分支拓扑。
|
|
// DensifyRiseSetCurves inserts geodesic samples using the projected distance
|
|
// consumed by map renderers. Rise/set solvers intentionally use a coarser time
|
|
// step for performance; using those raw vertices as SVG/GeoJSON linework makes
|
|
// high-latitude curves visibly angular even when the physical path is smooth.
|
|
// Endpoints, phase, direction, and segment topology are preserved exactly.
|
|
func DensifyRiseSetCurves(
|
|
curves []basic.OccultationRiseSetCurve,
|
|
maximumProjectedSpacingKM float64,
|
|
) []basic.OccultationRiseSetCurve {
|
|
if len(curves) == 0 || maximumProjectedSpacingKM <= 0 {
|
|
return curves
|
|
}
|
|
result := make([]basic.OccultationRiseSetCurve, len(curves))
|
|
for curveIndex, curve := range curves {
|
|
result[curveIndex] = basic.OccultationRiseSetCurve{
|
|
Phase: curve.Phase, Direction: curve.Direction,
|
|
Segments: make([][]basic.OccultationPathPoint, len(curve.Segments)),
|
|
}
|
|
for segmentIndex, segment := range curve.Segments {
|
|
result[curveIndex].Segments[segmentIndex] = densifyRiseSetSegment(segment, maximumProjectedSpacingKM)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// DensifyOccultationPathPoints 将相同的投影感知间距应用于单条月掩路径。
|
|
// DensifyOccultationPathPoints applies the same projection-aware spacing to a
|
|
// standalone geographic line such as a horizon connector. It keeps both
|
|
// endpoints and does not attach phase metadata to the result.
|
|
func DensifyOccultationPathPoints(
|
|
points []basic.OccultationPathPoint,
|
|
maximumProjectedSpacingKM float64,
|
|
) []basic.OccultationPathPoint {
|
|
return densifyRiseSetSegment(points, maximumProjectedSpacingKM)
|
|
}
|
|
|
|
func densifyRiseSetSegment(
|
|
segment []basic.OccultationPathPoint,
|
|
maximumProjectedSpacingKM float64,
|
|
) []basic.OccultationPathPoint {
|
|
if len(segment) < 2 {
|
|
return append([]basic.OccultationPathPoint(nil), segment...)
|
|
}
|
|
result := make([]basic.OccultationPathPoint, 0, len(segment)*2)
|
|
// The interpolation is geodesic on the sphere, while the spacing check is
|
|
// made in Web Mercator. A chord that is exactly at the projected limit can
|
|
// grow slightly after interpolation because Mercator is nonlinear in
|
|
// latitude. Keep a small margin so the emitted linework stays below the
|
|
// requested limit after projection.
|
|
effectiveSpacingKM := maximumProjectedSpacingKM * 0.75
|
|
for index, point := range segment {
|
|
result = append(result, point)
|
|
if index+1 >= len(segment) {
|
|
continue
|
|
}
|
|
next := segment[index+1]
|
|
steps := int(math.Ceil(projectedGeoPointDistanceKM(point, next) / effectiveSpacingKM))
|
|
if steps < 2 {
|
|
continue
|
|
}
|
|
for step := 1; step < steps; step++ {
|
|
fraction := float64(step) / float64(steps)
|
|
middle := interpolateOccultationGeoPoint(
|
|
geodata.GeoPoint{Longitude: point.Longitude, Latitude: point.Latitude},
|
|
geodata.GeoPoint{Longitude: next.Longitude, Latitude: next.Latitude},
|
|
fraction,
|
|
)
|
|
result = append(result, basic.OccultationPathPoint{
|
|
Time: point.Time.Add(time.Duration(float64(next.Time.Sub(point.Time)) * fraction)),
|
|
Longitude: middle.Longitude,
|
|
Latitude: middle.Latitude,
|
|
MoonAltitude: point.MoonAltitude + (next.MoonAltitude-point.MoonAltitude)*fraction,
|
|
WidthKM: point.WidthKM + (next.WidthKM-point.WidthKM)*fraction,
|
|
})
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func projectedGeoPointDistanceKM(first, second basic.OccultationPathPoint) float64 {
|
|
return geodata.WebMercatorDistanceKM(
|
|
geodata.GeoPoint{Longitude: first.Longitude, Latitude: first.Latitude},
|
|
geodata.GeoPoint{Longitude: second.Longitude, Latitude: second.Latitude},
|
|
)
|
|
}
|