feat: 完善水星与金星全球凌日搜索及制图导出
- 基于平均下合周期与节点相位预筛候选,减少无效星历计算;跳过无法求解的单个会合周期,避免有限覆盖星历导致搜索提前终止 - 引入影轴 γ、半影半径及地球尺度几何判定,结合站心可见性识别地心无接触、仅部分地区可见的掠凌 - 添加金星 243 年、水星 46 年周期族编号,并明确编号适用范围及未分配时的零值语义 - 为 SVG、GeoJSON 和 KML 增加站心部分凌日可见区,适配接触等时线、几何元数据、图层样式及取景;为地心无接触凌日事件增加正射图,按可见区球面面积加权居中
This commit is contained in:
41 files changed
+1554
-307
No files matched your search
@@ -78,7 +78,7 @@ func BrightLimbPositionAngleN(date time.Time, n int) float64 {
|
||||
return defaultModule.BrightLimbPositionAngleN(date, n)
|
||||
}
|
||||
|
||||
// ClosestTransit 最近一次地心金星凌日 / closest geocentric Venus transit.
|
||||
// ClosestTransit 最近一次全球金星凌日 / closest global Venus transit.
|
||||
func ClosestTransit(date time.Time) TransitInfo { return defaultModule.ClosestTransit(date) }
|
||||
|
||||
// CulminationTime 中天时刻,取 date 所在当地民用日 / culmination time, on the local civil day of date.
|
||||
@@ -178,10 +178,10 @@ func LastSuperiorConjunction(date time.Time) time.Time {
|
||||
return defaultModule.LastSuperiorConjunction(date)
|
||||
}
|
||||
|
||||
// LastTransit 上一次地心金星凌日 / previous geocentric Venus transit.
|
||||
// LastTransit 上一次全球金星凌日 / previous global Venus transit.
|
||||
func LastTransit(date time.Time) TransitInfo { return defaultModule.LastTransit(date) }
|
||||
|
||||
// LocalTransit 地心凌日在观测地的站心接触与凌甚 / topocentric contacts of a transit seen from the site.
|
||||
// LocalTransit 凌日在观测地的站心接触与凌甚 / topocentric contacts of a transit seen from the site.
|
||||
func LocalTransit(transit TransitInfo, lon, lat, height float64) LocalTransitInfo {
|
||||
return defaultModule.LocalTransit(transit, lon, lat, height)
|
||||
}
|
||||
@@ -227,7 +227,7 @@ func NextSuperiorConjunction(date time.Time) time.Time {
|
||||
return defaultModule.NextSuperiorConjunction(date)
|
||||
}
|
||||
|
||||
// NextTransit 下一次地心金星凌日 / next geocentric Venus transit.
|
||||
// NextTransit 下一次全球金星凌日 / next global Venus transit.
|
||||
func NextTransit(date time.Time) TransitInfo { return defaultModule.NextTransit(date) }
|
||||
|
||||
// ParallacticAngle 金星视差角(天顶方向角) / Venus parallactic angle.
|
||||
|
||||
+35
-16
@@ -6,12 +6,12 @@ import (
|
||||
"b612.me/astro/basic"
|
||||
)
|
||||
|
||||
// TransitInfo 地心金星凌日信息 / geocentric Venus transit information.
|
||||
// TransitInfo 全球金星凌日与地心接触 / global Venus transit and geocentric contacts.
|
||||
//
|
||||
// Start、Greatest、End、InternalStart、InternalEnd 都保持调用者输入的时区。
|
||||
// 内切接触不存在时 InternalStart / InternalEnd 为零值。
|
||||
// 地心接触不存在时对应时刻为零值;WindowStart、WindowEnd 只是站心求解窗口。
|
||||
// Start, Greatest, End, InternalStart, and InternalEnd preserve the caller's timezone.
|
||||
// InternalStart and InternalEnd are zero values when internal contacts do not exist.
|
||||
// Absent geocentric contacts are zero; WindowStart and WindowEnd are only a local search window.
|
||||
type TransitInfo struct {
|
||||
Valid bool
|
||||
|
||||
@@ -28,20 +28,26 @@ type TransitInfo struct {
|
||||
SunSemidiameterArcsec float64
|
||||
PlanetSemidiameterArcsec float64
|
||||
|
||||
HasInternal bool
|
||||
HasInternal bool
|
||||
HasExternal bool
|
||||
WindowStart time.Time
|
||||
WindowEnd time.Time
|
||||
Gamma float64
|
||||
PenumbralRadius float64
|
||||
Series basic.PlanetTransitSeries
|
||||
}
|
||||
|
||||
// NextTransit 下一次地心金星凌日 / next geocentric Venus transit.
|
||||
// NextTransit 下一次全球金星凌日 / next global Venus transit.
|
||||
func (module *Venus) NextTransit(date time.Time) TransitInfo {
|
||||
return transitInfoFromBasic(basic.NextVenusTransitWithInstance(basic.Date2JD(date.UTC()), module.inst), date.Location())
|
||||
}
|
||||
|
||||
// LastTransit 上一次地心金星凌日 / previous geocentric Venus transit.
|
||||
// LastTransit 上一次全球金星凌日 / previous global Venus transit.
|
||||
func (module *Venus) LastTransit(date time.Time) TransitInfo {
|
||||
return transitInfoFromBasic(basic.LastVenusTransitWithInstance(basic.Date2JD(date.UTC()), module.inst), date.Location())
|
||||
}
|
||||
|
||||
// ClosestTransit 最近一次地心金星凌日 / closest geocentric Venus transit.
|
||||
// ClosestTransit 最近一次全球金星凌日 / closest global Venus transit.
|
||||
func (module *Venus) ClosestTransit(date time.Time) TransitInfo {
|
||||
return transitInfoFromBasic(basic.ClosestVenusTransitWithInstance(basic.Date2JD(date.UTC()), module.inst), date.Location())
|
||||
}
|
||||
@@ -50,19 +56,25 @@ func transitInfoFromBasic(result basic.PlanetTransitResult, loc *time.Location)
|
||||
if !result.Valid {
|
||||
return TransitInfo{}
|
||||
}
|
||||
start := basic.JD2DateByZone(result.ExternalIngress, loc, false)
|
||||
greatest := basic.JD2DateByZone(result.Greatest, loc, false)
|
||||
end := basic.JD2DateByZone(result.ExternalEgress, loc, false)
|
||||
info := TransitInfo{
|
||||
Valid: true,
|
||||
Start: start,
|
||||
Greatest: greatest,
|
||||
End: end,
|
||||
Duration: end.Sub(start),
|
||||
MinimumSeparationArcsec: result.MinimumSeparationArcsec,
|
||||
SunSemidiameterArcsec: result.SunSemidiameterArcsec,
|
||||
PlanetSemidiameterArcsec: result.PlanetSemidiameterArcsec,
|
||||
HasInternal: result.HasInternal,
|
||||
HasExternal: result.HasExternal,
|
||||
WindowStart: basic.JD2DateByZone(result.WindowStart, loc, false),
|
||||
WindowEnd: basic.JD2DateByZone(result.WindowEnd, loc, false),
|
||||
Gamma: result.Gamma,
|
||||
PenumbralRadius: result.PenumbralRadius,
|
||||
Series: result.Series,
|
||||
}
|
||||
if result.HasExternal {
|
||||
info.Start = basic.JD2DateByZone(result.ExternalIngress, loc, false)
|
||||
info.End = basic.JD2DateByZone(result.ExternalEgress, loc, false)
|
||||
info.Duration = info.End.Sub(info.Start)
|
||||
}
|
||||
if result.HasInternal {
|
||||
info.InternalStart = basic.JD2DateByZone(result.InternalIngress, loc, false)
|
||||
@@ -81,7 +93,7 @@ type LocalTransitInfo = basic.PlanetTransitLocalInfo
|
||||
// LocalTransitContact 站心凌日接触 / topocentric transit contact.
|
||||
type LocalTransitContact = basic.PlanetTransitLocalContactInfo
|
||||
|
||||
// LocalTransit 地心凌日 transit 在观测地的站心接触与凌甚 / topocentric contacts of transit seen from the site.
|
||||
// LocalTransit 凌日 transit 在观测地的站心接触与凌甚 / topocentric contacts of transit seen from the site.
|
||||
//
|
||||
// lon、lat 为度,东经、北纬为正;height 为海拔米。
|
||||
// lon and lat are in degrees, east and north positive; height is in meters.
|
||||
@@ -89,8 +101,15 @@ func (module *Venus) LocalTransit(transit TransitInfo, lon, lat, height float64)
|
||||
if !transit.Valid {
|
||||
return LocalTransitInfo{}
|
||||
}
|
||||
result := basic.PlanetTransitResultFromTimes(2,
|
||||
transit.Start, transit.InternalStart, transit.Greatest, transit.InternalEnd, transit.End)
|
||||
local := basic.PlanetTransitLocalWithInstance(result, basic.Observer{Longitude: lon, Latitude: lat, Height: height}, module.inst)
|
||||
local := basic.PlanetTransitLocalWithInstance(transitResultFromInfo(transit), basic.Observer{Longitude: lon, Latitude: lat, Height: height}, module.inst)
|
||||
return basic.PlanetTransitLocalInfoFromResult(local, transit.Greatest.Location())
|
||||
}
|
||||
|
||||
// transitResultFromInfo 按一触、四触是否为零选择地心接触或求解窗口还原 basic 结果。
|
||||
func transitResultFromInfo(transit TransitInfo) basic.PlanetTransitResult {
|
||||
if transit.Start.IsZero() && transit.End.IsZero() {
|
||||
return basic.PlanetTransitResultFromWindow(2, transit.WindowStart, transit.Greatest, transit.WindowEnd)
|
||||
}
|
||||
return basic.PlanetTransitResultFromTimes(2,
|
||||
transit.Start, transit.InternalStart, transit.Greatest, transit.InternalEnd, transit.End)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package venus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGlobalTransitWrapper(tester *testing.T) {
|
||||
zone := time.FixedZone("UTC+8", 8*3600)
|
||||
info := NextTransit(time.Date(2854, 1, 1, 0, 0, 0, 0, zone))
|
||||
if !info.Valid || info.Greatest.Year() != 2854 || info.HasExternal || !info.Start.IsZero() || !info.End.IsZero() || info.Duration != 0 || info.Series.Number != 4 {
|
||||
tester.Fatalf("invalid global transit: %+v", info)
|
||||
}
|
||||
if info.Greatest.Location() != zone || info.WindowStart.Location() != zone || info.WindowEnd.Location() != zone {
|
||||
tester.Fatal("global transit lost the caller's timezone")
|
||||
}
|
||||
local := LocalTransit(info, -135, -60, 0)
|
||||
if !local.Valid || !local.Visible || local.HasInternal || local.Greatest.Time.Location() != zone {
|
||||
tester.Fatalf("invalid local partial transit: %+v", local)
|
||||
}
|
||||
for _, result := range []TransitInfo{ClosestTransit(info.Greatest), LastTransit(info.Greatest.Add(time.Minute)), NextTransit(info.Greatest.Add(-time.Minute))} {
|
||||
difference := result.Greatest.Sub(info.Greatest)
|
||||
if !result.Valid || difference > time.Second || difference < -time.Second {
|
||||
tester.Fatal("global event lost at a query boundary")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalTransitRequiresContactsOrWindow(tester *testing.T) {
|
||||
info := NextTransit(time.Date(2854, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
info.WindowStart, info.WindowEnd = time.Time{}, time.Time{}
|
||||
if local := LocalTransit(info, -135, -60, 0); local.Valid {
|
||||
tester.Fatal("partial transit without a window must be rejected")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user