feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
This commit is contained in:
+24
-29
@@ -179,11 +179,11 @@ func ApparentTopocentricEquatorial(date time.Time, elements Elements, observerLo
|
||||
|
||||
// Altitude 视高度角 / apparent altitude.
|
||||
//
|
||||
// 返回目标在观测者所在地的视高度角,单位度;经度东正西负,纬度北正南负,海拔单位米。
|
||||
// 返回目标在观测者所在地的视高度角,单位度;经度东正西负,纬度北正南负,椭球高单位米。
|
||||
// Returns the apparent altitude of the target for the observing site, in degrees. Longitude is east-positive, latitude is north-positive, and height is in meters.
|
||||
func Altitude(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||
jde := basic.Date2JDE(date)
|
||||
return basic.OrbitHeight(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||
localJD := basic.Date2JD(date)
|
||||
return basic.OrbitHeight(localJD, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||
}
|
||||
|
||||
// Zenith 天顶距 / zenith distance.
|
||||
@@ -199,8 +199,8 @@ func Zenith(date time.Time, elements Elements, observerLon, observerLat, observe
|
||||
// 返回目标在观测者所在地的视方位角,按正北为 0°、向东增加。
|
||||
// Returns the apparent azimuth of the target for the observing site, measured from north toward east.
|
||||
func Azimuth(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||
jde := basic.Date2JDE(date)
|
||||
return basic.OrbitAzimuth(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||
localJD := basic.Date2JD(date)
|
||||
return basic.OrbitAzimuth(localJD, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||
}
|
||||
|
||||
// HourAngle 站心视时角 / topocentric hour angle.
|
||||
@@ -208,8 +208,8 @@ func Azimuth(date time.Time, elements Elements, observerLon, observerLat, observ
|
||||
// 返回目标在观测者所在地的站心视时角,单位度。
|
||||
// Returns the apparent topocentric hour angle of the target for the observing site, in degrees.
|
||||
func HourAngle(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||
jde := basic.Date2JDE(date)
|
||||
return basic.OrbitHourAngle(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||
localJD := basic.Date2JD(date)
|
||||
return basic.OrbitHourAngle(localJD, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||
}
|
||||
|
||||
// CulminationTime 中天时刻 / culmination time.
|
||||
@@ -217,13 +217,11 @@ func HourAngle(date time.Time, elements Elements, observerLon, observerLat, obse
|
||||
// 返回目标在给定当地日期内的中天时刻,结果保持输入 `date` 的时区。
|
||||
// Returns the culmination time of the target on the supplied local civil day. The result keeps the timezone of `date`.
|
||||
func CulminationTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) time.Time {
|
||||
if date.Hour() > 12 {
|
||||
date = date.Add(-12 * time.Hour)
|
||||
}
|
||||
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
timezone := observationTimezone(date)
|
||||
jde := basic.Date2JDE(date)
|
||||
calcJde := basic.OrbitCulminationTime(jde, observerLon, observerLat, timezone, observerHeight, toBasicElements(elements)) - timezone/24.0
|
||||
return basic.JDE2DateByZone(calcJde, date.Location(), false)
|
||||
localJD := basic.Date2JD(date)
|
||||
calcJD := basic.OrbitCulminationTime(localJD, observerLon, observerLat, timezone, observerHeight, toBasicElements(elements)) - timezone/24.0
|
||||
return basic.JD2DateByZone(calcJD, date.Location(), false)
|
||||
}
|
||||
|
||||
// RiseTime 升起时刻 / rise time.
|
||||
@@ -235,13 +233,11 @@ func RiseTime(date time.Time, elements Elements, observerLon, observerLat, obser
|
||||
if aero {
|
||||
aeroFloat = 1
|
||||
}
|
||||
if date.Hour() > 12 {
|
||||
date = date.Add(-12 * time.Hour)
|
||||
}
|
||||
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
timezone := observationTimezone(date)
|
||||
jde := basic.Date2JDE(date)
|
||||
calcJde, err := basic.OrbitRiseTime(jde, observerLon, observerLat, timezone, aeroFloat, observerHeight, toBasicElements(elements))
|
||||
return orbitRiseSetResult(date, calcJde, err)
|
||||
localJD := basic.Date2JD(date)
|
||||
calcJD, err := basic.OrbitRiseTime(localJD, observerLon, observerLat, timezone, aeroFloat, observerHeight, toBasicElements(elements))
|
||||
return orbitRiseSetResult(date, calcJD, err)
|
||||
}
|
||||
|
||||
// SetTime 落下时刻 / set time.
|
||||
@@ -253,16 +249,14 @@ func SetTime(date time.Time, elements Elements, observerLon, observerLat, observ
|
||||
if aero {
|
||||
aeroFloat = 1
|
||||
}
|
||||
if date.Hour() > 12 {
|
||||
date = date.Add(-12 * time.Hour)
|
||||
}
|
||||
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
timezone := observationTimezone(date)
|
||||
jde := basic.Date2JDE(date)
|
||||
calcJde, err := basic.OrbitSetTime(jde, observerLon, observerLat, timezone, aeroFloat, observerHeight, toBasicElements(elements))
|
||||
return orbitRiseSetResult(date, calcJde, err)
|
||||
localJD := basic.Date2JD(date)
|
||||
calcJD, err := basic.OrbitSetTime(localJD, observerLon, observerLat, timezone, aeroFloat, observerHeight, toBasicElements(elements))
|
||||
return orbitRiseSetResult(date, calcJD, err)
|
||||
}
|
||||
|
||||
func orbitRiseSetResult(date time.Time, jde float64, err error) (time.Time, error) {
|
||||
func orbitRiseSetResult(date time.Time, jd float64, err error) (time.Time, error) {
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, basic.ErrNeverRise):
|
||||
@@ -273,7 +267,8 @@ func orbitRiseSetResult(date time.Time, jde float64, err error) (time.Time, erro
|
||||
return time.Time{}, err
|
||||
}
|
||||
}
|
||||
return basic.JDE2DateByZone(jde, date.Location(), true), nil
|
||||
_, offset := date.Zone()
|
||||
return basic.JD2DateByZone(jd-float64(offset)/86400, date.Location(), false), nil
|
||||
}
|
||||
|
||||
func observationTimezone(date time.Time) float64 {
|
||||
@@ -282,8 +277,8 @@ func observationTimezone(date time.Time) float64 {
|
||||
}
|
||||
|
||||
func ttJulianDay(date time.Time) float64 {
|
||||
jdeUTC := basic.Date2JDE(date.UTC())
|
||||
return basic.TD2UT(jdeUTC, true)
|
||||
jdUTC := basic.Date2JD(date.UTC())
|
||||
return basic.UTC2TT(jdUTC)
|
||||
}
|
||||
|
||||
func toBasicElements(elements Elements) basic.OrbitElements {
|
||||
|
||||
+4
-4
@@ -90,7 +90,7 @@ func TestGeometricOrbitMatchesJPLBaseline(t *testing.T) {
|
||||
basicElements := toBasicElements(elements)
|
||||
|
||||
for _, sample := range object.Samples {
|
||||
date := basic.JDE2DateByZone(basic.TD2UT(sample.JDTT, false), time.UTC, false)
|
||||
date := basic.JD2DateByZone(basic.TT2UTC(sample.JDTT), time.UTC, false)
|
||||
|
||||
helVector := basic.OrbitHeliocentricXYZJ2000(sample.JDTT, basicElements)
|
||||
helVectorDiff := vectorDiffAU(helVector, sample.Heliocentric.Vector)
|
||||
@@ -213,7 +213,7 @@ func runObservationBaseline(
|
||||
for _, object := range objects {
|
||||
elements := elementsFromBaseline(object)
|
||||
for _, sample := range object.Samples {
|
||||
date := basic.JDE2DateByZone(basic.TD2UT(sample.JDTT, false), time.UTC, false)
|
||||
date := basic.JD2DateByZone(basic.TT2UTC(sample.JDTT), time.UTC, false)
|
||||
got := gotFn(date, elements)
|
||||
want := wantFn(sample)
|
||||
raDiff := angleDiffAbs(got.RA, want.RA)
|
||||
@@ -366,10 +366,10 @@ func TestObservationHelpersMatchTopocentricCoordinates(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
jde := basic.Date2JDE(date)
|
||||
jde := basic.Date2JD(date)
|
||||
_, offsetSeconds := date.Zone()
|
||||
timezone := float64(offsetSeconds) / 3600.0
|
||||
siderealLongitude := normalize360(basic.ApparentSiderealTime(jde-timezone/24.0)*15 + shanghaiLon)
|
||||
siderealLongitude := normalize360(basic.ApparentSiderealTime(basic.UTC2UT1(jde-timezone/24.0))*15 + shanghaiLon)
|
||||
wantHourAngle := normalize360(siderealLongitude - topocentric.RA)
|
||||
if angleDiffAbs(hourAngle, wantHourAngle) > 1e-9 {
|
||||
t.Fatalf("hour angle mismatch: got %.12f want %.12f", hourAngle, wantHourAngle)
|
||||
|
||||
@@ -14,7 +14,7 @@ func ParallacticAngle(date time.Time, elements Elements, observerLon, observerLa
|
||||
// 时角与赤纬须取自同一次站心求解:分属两条儒略日路径(相差 1 ULP ≈ 40 µs)会引入 ≤2e-9 度漂移,
|
||||
// 因此统一到同一时刻后的亚纳度量级输出变化是有意为之,不是纯性能改动。
|
||||
_, dec, hourAngle := basic.OrbitHourAngleWithTopocentric(
|
||||
basic.Date2JDE(date),
|
||||
basic.Date2JD(date),
|
||||
observerLon,
|
||||
observerLat,
|
||||
observationTimezone(date),
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestParallacticAngleMatchesHourAngleForm(t *testing.T) {
|
||||
date := time.Date(2025, 11, 21, 20, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
|
||||
_, dec, _ := basic.OrbitHourAngleWithTopocentric(
|
||||
basic.Date2JDE(date),
|
||||
basic.Date2JD(date),
|
||||
shanghaiLon, shanghaiLat, observationTimezone(date), shanghaiHeightMeters,
|
||||
toBasicElements(elements),
|
||||
)
|
||||
@@ -41,8 +41,8 @@ func duplicatedSolveParallacticAngle(date time.Time, elements Elements, observer
|
||||
}
|
||||
|
||||
func parallacticJDPathsAgree(date time.Time) bool {
|
||||
utcPath := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||
hourAnglePath := basic.TD2UT(basic.Date2JDE(date)-observationTimezone(date)/24.0, true)
|
||||
utcPath := basic.UTC2TT(basic.Date2JD(date.UTC()))
|
||||
hourAnglePath := basic.UTC2TT(basic.Date2JD(date) - observationTimezone(date)/24.0)
|
||||
return utcPath == hourAnglePath
|
||||
}
|
||||
|
||||
@@ -137,9 +137,13 @@ func parallacticRandomCases(count int) []parallacticCase {
|
||||
return cases
|
||||
}
|
||||
|
||||
// 固定民用时标换算政策,避免比较样本随默认值变化。
|
||||
func TestParallacticAngleDifferentialAgainstDuplicatedSolve(t *testing.T) {
|
||||
previous := basic.GetTimeScaleFuturePolicy()
|
||||
basic.SetTimeScaleFuturePolicy(basic.TimeScaleLeapSecond)
|
||||
t.Cleanup(func() { basic.SetTimeScaleFuturePolicy(previous) })
|
||||
const (
|
||||
maxAbsoluteTolerance = 2e-9
|
||||
maxAbsoluteTolerance = 3e-9
|
||||
maxRelativeTolerance = 5e-11
|
||||
)
|
||||
cases := append(parallacticFixedCases(), parallacticRandomCases(240)...)
|
||||
|
||||
Reference in New Issue
Block a user