feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package tools
|
||||
|
||||
import "math"
|
||||
|
||||
// DistanceUnit 距离输入单位 / a unit accepted for distance input.
|
||||
type DistanceUnit uint8
|
||||
|
||||
const (
|
||||
// DistanceParsec 秒差距 / parsec.
|
||||
DistanceParsec DistanceUnit = iota
|
||||
// DistanceLightYear 光年 / light-year.
|
||||
DistanceLightYear
|
||||
// DistanceAU 天文单位 / astronomical unit.
|
||||
DistanceAU
|
||||
)
|
||||
|
||||
// 天文单位与光年取 IAU 定义值,秒差距由精确关系 648000/π 天文单位导出。
|
||||
const (
|
||||
AstronomicalUnitKilometers = 149597870.7
|
||||
LightYearKilometers = 9460730472580.8
|
||||
parsecAU = 648000 / math.Pi
|
||||
)
|
||||
|
||||
// DistanceToParsecs 把给定单位的距离换算为秒差距 / converts a distance in the given unit to parsecs.
|
||||
//
|
||||
// 非正数、NaN 与未知单位返回 NaN。
|
||||
func DistanceToParsecs(value float64, unit DistanceUnit) float64 {
|
||||
if math.IsNaN(value) || math.IsInf(value, 0) || value <= 0 {
|
||||
return math.NaN()
|
||||
}
|
||||
switch unit {
|
||||
case DistanceParsec:
|
||||
return value
|
||||
case DistanceLightYear:
|
||||
return value * LightYearKilometers / AstronomicalUnitKilometers / parsecAU
|
||||
case DistanceAU:
|
||||
return value / parsecAU
|
||||
}
|
||||
return math.NaN()
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 参考值由同一组 IAU 定义值独立推出,不使用 9 位凑整常数。
|
||||
const (
|
||||
testPcPerLy = LightYearKilometers / AstronomicalUnitKilometers / parsecAU
|
||||
testAuPerLy = LightYearKilometers / AstronomicalUnitKilometers
|
||||
)
|
||||
|
||||
func assertDistanceClose(t *testing.T, name string, got, want, tolerance float64) {
|
||||
t.Helper()
|
||||
if math.Abs(got-want) > tolerance {
|
||||
t.Fatalf("%s mismatch: got %.15f want %.15f", name, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistanceToParsecsUnits(t *testing.T) {
|
||||
assertDistanceClose(t, "1 pc", DistanceToParsecs(1, DistanceParsec), 1, 1e-15)
|
||||
assertDistanceClose(t, "1 ly in pc", DistanceToParsecs(1, DistanceLightYear), testPcPerLy, 1e-15)
|
||||
assertDistanceClose(t, "1 ly in pc 参考 0.3066", testPcPerLy, 0.306601394, 1e-9)
|
||||
assertDistanceClose(t, "1 pc in ly 参考 3.261563777", 1/testPcPerLy, 3.261563777, 1e-9)
|
||||
assertDistanceClose(t, "1 ly in au 参考 63241.077084", DistanceToParsecs(1, DistanceLightYear)*parsecAU, 63241.077084, 1e-6)
|
||||
assertDistanceClose(t, "1 pc in au 参考 206264.806247096", DistanceToParsecs(1, DistanceParsec)*parsecAU, 206264.806247096, 1e-9)
|
||||
assertDistanceClose(t, "648000/pi au in pc", DistanceToParsecs(parsecAU, DistanceAU), 1, 1e-14)
|
||||
}
|
||||
|
||||
func TestDistanceToParsecsRoundTrip(t *testing.T) {
|
||||
for _, pc := range []float64{0.0001, 1, 2.637, 5.7803, 1000} {
|
||||
assertDistanceClose(t, "pc round trip", DistanceToParsecs(pc, DistanceParsec), pc, 1e-15)
|
||||
ly := pc / testPcPerLy
|
||||
assertDistanceClose(t, "pc->ly->pc", DistanceToParsecs(ly, DistanceLightYear), pc, 1e-12)
|
||||
assertDistanceClose(t, "au round trip", DistanceToParsecs(pc*parsecAU, DistanceAU), pc, 1e-12)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDistanceToParsecsRejectsInvalidInput(t *testing.T) {
|
||||
for _, value := range []float64{0, -1, math.NaN(), math.Inf(1), math.Inf(-1)} {
|
||||
if got := DistanceToParsecs(value, DistanceParsec); !math.IsNaN(got) {
|
||||
t.Fatalf("DistanceToParsecs(%v, parsec) = %v, want NaN", value, got)
|
||||
}
|
||||
}
|
||||
if got := DistanceToParsecs(1, DistanceUnit(200)); !math.IsNaN(got) {
|
||||
t.Fatalf("DistanceToParsecs(1, unknown unit) = %v, want NaN", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user