Files
astro/internal/svgmap/map_test.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

116 lines
4.4 KiB
Go

package svgmap
import (
"fmt"
"math"
"strings"
"testing"
)
func TestPolarProjectionPlacesPoleAtCenterAndEquatorOnFrame(t *testing.T) {
frame := Frame{X: 10, Y: 20, Width: 300, Height: 300, Projection: ProjectionNorthPolar}
x, y, ok := frame.Project(123, 90)
if !ok || math.Abs(x-160) > 1e-9 || math.Abs(y-170) > 1e-9 {
t.Fatalf("north pole = %.3f %.3f %v, want map center", x, y, ok)
}
// 事件经线朝下,中心经线在赤道上落在圆盘最下方。
x, y, ok = frame.Project(0, 0)
if !ok || math.Abs(x-160) > 1e-9 || math.Abs(y-320) > 1e-9 {
t.Fatalf("prime-meridian equator = %.3f %.3f %v, want bottom edge", x, y, ok)
}
if _, _, ok := frame.Project(0, -1); ok {
t.Fatal("north-polar projection accepted a southern-hemisphere point")
}
}
func TestProjectionClippingDoesNotSplitPolarAntimeridian(t *testing.T) {
points := []GeoPoint{{Longitude: 170, Latitude: 70}, {Longitude: -170, Latitude: 70}}
segments := PolylineSegments(points, ClipView{Projection: ProjectionNorthPolar})
if len(segments) != 1 || len(segments[0]) != 2 {
t.Fatalf("polar antimeridian segments = %#v, want one continuous segment", segments)
}
segments = PolylineSegments(points, ClipView{Projection: ProjectionEquirectangular})
if len(segments) != 2 {
t.Fatalf("equirectangular antimeridian segment count = %d, want 2", len(segments))
}
}
func TestNaturalEarthAssetsAndPolarClip(t *testing.T) {
if len(equirectangularLandPath) < 150000 || len(northPolarLandPath) < 100000 || len(southPolarLandPath) < 40000 {
t.Fatalf("unexpected embedded map sizes: world=%d north=%d south=%d",
len(equirectangularLandPath), len(northPolarLandPath), len(southPolarLandPath))
}
frame := Frame{Width: 300, Height: 300, Projection: ProjectionSouthPolar}
var builder strings.Builder
frame.WriteOcean(&builder)
frame.WriteLand(&builder, "clip")
frame.WriteFrame(&builder)
for _, want := range []string{`class="map-ocean"`, `class="land"`, `class="map-frame"`, `<circle`} {
if !strings.Contains(builder.String(), want) {
t.Fatalf("polar map output missing %q", want)
}
}
}
// 极地投影必须与“从极点上方俯视”一致,并把事件经线转到可读的一侧:
// 北极图的极点在圆心,事件经线落在正下方,于是事件附近是北在上、东在右。
// 用 +sin(λ−C) 会得到镜像图,东京会跑到北京西边。
func TestPolarProjectionKeepsTrueEastWest(t *testing.T) {
frame := Frame{Width: 200, Height: 200, Projection: ProjectionNorthPolar}
centreY := frame.Height / 2
// 极点落在圆心。
_, poleY, _ := frame.Project(0, 90)
if math.Abs(poleY-centreY) > 1e-9 {
t.Fatalf("pole projects to y=%.3f, want centre %.3f", poleY, centreY)
}
// 事件经线朝下:赤道上中心经线的点落在圆盘最下方。
_, meridianY, _ := frame.Project(0, 0)
if meridianY <= centreY {
t.Fatalf("centre meridian projects to y=%.1f, want below centre %.1f", meridianY, centreY)
}
// 事件附近东在右:同纬度上偏东的点横坐标更大。
west, _, _ := frame.Project(-10, 45)
east, _, _ := frame.Project(10, 45)
if east <= west {
t.Fatalf("10E projects to x=%.1f, want right of 10W at %.1f", east, west)
}
}
func TestCenteredEquirectangularLandCoversWholeFrame(t *testing.T) {
for _, center := range []float64{-179.5, -104.145, -90, -1, 1, 90, 104.145, 179.5} {
frame := Frame{
X: 57, Y: 184, Width: 360, Height: 180,
Projection: ProjectionEquirectangular, CenterLongitude: center,
}
var builder strings.Builder
frame.WriteLand(&builder, "clip")
offsets := centeredLandTileOffsets(t, builder.String())
if len(offsets) == 0 {
t.Fatalf("center %.3f drew no land tiles", center)
}
left, right := math.Inf(1), math.Inf(-1)
for _, offset := range offsets {
left = math.Min(left, offset)
right = math.Max(right, offset+frame.Width)
}
if left > frame.X+1e-6 || right < frame.X+frame.Width-1e-6 {
t.Fatalf("center %.3f covers longitude band [%.3f,%.3f], frame needs [%.3f,%.3f]",
center, left, right, frame.X, frame.X+frame.Width)
}
}
}
func centeredLandTileOffsets(t *testing.T, document string) []float64 {
t.Helper()
offsets := make([]float64, 0, 2)
for _, piece := range strings.Split(document, `<use href="#land-equirectangular-tile" transform="translate(`)[1:] {
var x, y float64
if _, err := fmt.Sscanf(piece, "%f %f", &x, &y); err != nil {
t.Fatalf("parse land tile translate: %v", err)
}
offsets = append(offsets, x)
}
return offsets
}