feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package occultationgeo
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/astro/basic"
|
||||
"b612.me/astro/internal/geodata"
|
||||
)
|
||||
|
||||
var (
|
||||
saturn20250105Once sync.Once
|
||||
saturn20250105Paths []basic.PlanetOccultationPath
|
||||
)
|
||||
|
||||
func saturn20250105Path(tb testing.TB) basic.PlanetOccultationPath {
|
||||
tb.Helper()
|
||||
saturn20250105Once.Do(func() {
|
||||
zone := time.FixedZone("UTC+8", 8*60*60)
|
||||
start := time.Date(2025, time.January, 5, 0, 0, 0, 0, zone)
|
||||
paths, err := basic.FindPlanetOccultationPaths(
|
||||
start, start.Add(24*time.Hour), basic.OccultationSaturn,
|
||||
basic.OccultationPathOptions{
|
||||
Step: 20 * time.Minute, TargetSpacingKM: 900,
|
||||
DisableFootprints: true, IncludeFootprintTimeline: true,
|
||||
FootprintTimelineStep: 5 * time.Minute, RiseSetStep: time.Minute,
|
||||
},
|
||||
)
|
||||
if err != nil || len(paths) != 1 {
|
||||
tb.Fatalf("FindPlanetOccultationPaths() paths=%d err=%v", len(paths), err)
|
||||
}
|
||||
saturn20250105Paths = paths
|
||||
})
|
||||
return saturn20250105Paths[0]
|
||||
}
|
||||
|
||||
func BenchmarkVisibleBandPolygonsSaturn20250105(b *testing.B) {
|
||||
path := saturn20250105Path(b)
|
||||
b.ResetTimer()
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
if _, _, err := VisibleBandPolygonsFromContours(
|
||||
path.PartialBandFootprints, path.PartialBandContours,
|
||||
path.NorthernLimit, path.SouthernLimit, path.RiseSetCurves,
|
||||
); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkVisibleBandPolygonsAnalyticSaturn20250105(b *testing.B) {
|
||||
path := saturn20250105Path(b)
|
||||
b.ResetTimer()
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
if _, _, err := VisibleBandPolygonsFromAnalyticContours(
|
||||
path.PartialBandFootprints, path.PartialBandContours, path.PartialVisibilityContours,
|
||||
path.NorthernLimit, path.SouthernLimit, path.RiseSetCurves,
|
||||
); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFootprintSweepPolygonsSaturn20250105(b *testing.B) {
|
||||
path := saturn20250105Path(b)
|
||||
b.ResetTimer()
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
if _, err := footprintSweepPolygons(
|
||||
path.PartialBandFootprints, path.NorthernLimit, path.SouthernLimit,
|
||||
); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFootprintOpenSweepSaturn20250105(b *testing.B) {
|
||||
path := saturn20250105Path(b)
|
||||
b.ResetTimer()
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
if _, err := footprintOpenSweepPolygons(path.PartialBandFootprints); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFootprintVisibleUnionSaturn20250105(b *testing.B) {
|
||||
path := saturn20250105Path(b)
|
||||
b.ResetTimer()
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
if polygons := footprintVisibleUnionPolygons(path.PartialBandFootprints); len(polygons) == 0 {
|
||||
b.Fatal("empty visible union")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func occultationBenchChainPolygons(count, points int) [][]geodata.GeoPoint {
|
||||
polygons := make([][]geodata.GeoPoint, 0, count)
|
||||
for index := 0; index < count; index++ {
|
||||
center := geodata.GeoPoint{
|
||||
Longitude: -0.5 + float64(index)*1.5,
|
||||
Latitude: 40,
|
||||
}
|
||||
ring := make([]geodata.GeoPoint, 0, points+1)
|
||||
for step := 0; step <= points; step++ {
|
||||
angle := 2 * math.Pi * float64(step) / float64(points)
|
||||
ring = append(ring, geodata.GeoPoint{
|
||||
Longitude: center.Longitude + 0.5*math.Cos(angle),
|
||||
Latitude: center.Latitude + 0.5*math.Sin(angle),
|
||||
})
|
||||
}
|
||||
polygons = append(polygons, ring)
|
||||
}
|
||||
return polygons
|
||||
}
|
||||
|
||||
func BenchmarkMergeTouchingVisiblePolygonsChain(b *testing.B) {
|
||||
b.Run("count=12/points=16", func(b *testing.B) {
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
polygons := occultationBenchChainPolygons(12, 16)
|
||||
if merged := mergeTouchingVisiblePolygons(polygons); len(merged) != 1 {
|
||||
b.Fatalf("merged=%d, want one face", len(merged))
|
||||
}
|
||||
}
|
||||
})
|
||||
b.Run("count=24/points=16", func(b *testing.B) {
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
polygons := occultationBenchChainPolygons(24, 16)
|
||||
if merged := mergeTouchingVisiblePolygons(polygons); len(merged) != 1 {
|
||||
b.Fatalf("merged=%d, want one face", len(merged))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func occultationBenchConstrainInput(points int) (parent, child [][]geodata.GeoPoint) {
|
||||
ring := make([]geodata.GeoPoint, 0, points+1)
|
||||
childRing := make([]geodata.GeoPoint, 0, points+1)
|
||||
for step := 0; step <= points; step++ {
|
||||
angle := 2 * math.Pi * float64(step) / float64(points)
|
||||
longitude := -70 + 20*math.Cos(angle)
|
||||
latitude := -40 + 8*math.Sin(angle)
|
||||
ring = append(ring, geodata.GeoPoint{Longitude: longitude, Latitude: latitude})
|
||||
childRing = append(childRing, geodata.GeoPoint{
|
||||
Longitude: longitude + 0.02*math.Cos(angle),
|
||||
Latitude: latitude + 0.02*math.Sin(angle),
|
||||
})
|
||||
}
|
||||
return [][]geodata.GeoPoint{ring}, [][]geodata.GeoPoint{childRing}
|
||||
}
|
||||
|
||||
func BenchmarkConstrainPolygonsWithinSmallBreach(b *testing.B) {
|
||||
b.Run("points=512", func(b *testing.B) {
|
||||
parent, child := occultationBenchConstrainInput(512)
|
||||
b.ResetTimer()
|
||||
for iteration := 0; iteration < b.N; iteration++ {
|
||||
if repaired := ConstrainPolygonsWithin(parent, child); len(repaired) == 0 {
|
||||
b.Fatal("empty repair")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user