- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息 - 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算 - 新增木星伽利略卫星位置、现象与接触事件计算 - 新增恒星星表、星座判定、自行修正与观测辅助能力 - 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包 - 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算 - 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试 - 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试 - 更新中文和英文 README,补充示例、精度说明、SVG 配图
157 lines
4.8 KiB
Go
157 lines
4.8 KiB
Go
package svg
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"b612.me/astro/basic"
|
|
eclipsecore "b612.me/astro/eclipse"
|
|
)
|
|
|
|
type SolarEclipseRadiusModel = eclipsecore.SolarEclipseRadiusModel
|
|
|
|
type SolarEclipseType = eclipsecore.SolarEclipseType
|
|
|
|
type LocalSolarEclipseContactPoint = eclipsecore.LocalSolarEclipseContactPoint
|
|
|
|
type LocalSolarEclipseInfo = eclipsecore.LocalSolarEclipseInfo
|
|
|
|
const (
|
|
SolarEclipseModelIAUSingleK = eclipsecore.SolarEclipseModelIAUSingleK
|
|
SolarEclipseModelNASABulletinSplitK = eclipsecore.SolarEclipseModelNASABulletinSplitK
|
|
|
|
SolarEclipseNone = eclipsecore.SolarEclipseNone
|
|
SolarEclipsePartial = eclipsecore.SolarEclipsePartial
|
|
SolarEclipseAnnular = eclipsecore.SolarEclipseAnnular
|
|
SolarEclipseTotal = eclipsecore.SolarEclipseTotal
|
|
SolarEclipseHybrid = eclipsecore.SolarEclipseHybrid
|
|
)
|
|
|
|
func localSolarEclipseInfoFromDiagram(
|
|
diagram basic.LocalSolarEclipseDiagramResult,
|
|
lon, lat, height float64,
|
|
location *time.Location,
|
|
) LocalSolarEclipseInfo {
|
|
info := localSolarEclipseInfoFieldsFromBasic(diagram.Eclipse, lon, lat, height, location)
|
|
info.ContactPoints = localSolarEclipseContactPointsFromFrames(diagram.Frames, location)
|
|
return info
|
|
}
|
|
|
|
func localSolarEclipseInfoFieldsFromBasic(
|
|
result basic.LocalSolarEclipseResult,
|
|
lon, lat, height float64,
|
|
location *time.Location,
|
|
) LocalSolarEclipseInfo {
|
|
visibleThreshold := localSolarEclipseVisibilityThreshold(height, lat)
|
|
return LocalSolarEclipseInfo{
|
|
Model: mapBasicSolarEclipseModel(result.Model),
|
|
Type: mapBasicSolarEclipseType(result.Type),
|
|
Longitude: lon,
|
|
Latitude: lat,
|
|
Height: height,
|
|
GreatestEclipse: solarEclipseTTJDEToTime(result.GreatestEclipse, location),
|
|
PartialStart: solarEclipseTTJDEToTime(result.PartialStart, location),
|
|
PartialEnd: solarEclipseTTJDEToTime(result.PartialEnd, location),
|
|
CentralStart: solarEclipseTTJDEToTime(result.CentralStart, location),
|
|
CentralEnd: solarEclipseTTJDEToTime(result.CentralEnd, location),
|
|
Magnitude: result.Magnitude,
|
|
Obscuration: result.Obscuration,
|
|
Separation: result.Separation,
|
|
SunAltitude: result.SunAltitude,
|
|
SunAzimuth: result.SunAzimuth,
|
|
VisibleAtGreatest: result.SunAltitude > visibleThreshold,
|
|
HasPartial: result.HasPartial,
|
|
HasCentral: result.HasCentral,
|
|
HasAnnular: result.HasAnnular,
|
|
HasTotal: result.HasTotal,
|
|
}
|
|
}
|
|
|
|
func localSolarEclipseContactPointsFromFrames(
|
|
frames []basic.LocalSolarEclipseDiagramFrame,
|
|
location *time.Location,
|
|
) []LocalSolarEclipseContactPoint {
|
|
contacts := make([]LocalSolarEclipseContactPoint, 0, 4)
|
|
for _, frame := range frames {
|
|
for _, label := range localSolarEclipseFrameLabels(frame) {
|
|
switch label {
|
|
case "C1", "C2", "C3", "C4":
|
|
contactPA := frame.PositionAngle
|
|
if (label == "C2" || label == "C3") && frame.MoonRadius >= frame.SunRadius {
|
|
contactPA = normalizeSolarEclipseDegree360(contactPA + 180)
|
|
}
|
|
contacts = append(contacts, LocalSolarEclipseContactPoint{
|
|
Label: label,
|
|
Time: solarEclipseTTJDEToTime(frame.JDE, location),
|
|
ContactPositionAngle: contactPA,
|
|
ContactClockwiseAngle: normalizeSolarEclipseDegree360(360 - contactPA),
|
|
MoonCenterPositionAngle: frame.PositionAngle,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return contacts
|
|
}
|
|
|
|
func localSolarEclipseFrameLabels(frame basic.LocalSolarEclipseDiagramFrame) []string {
|
|
if len(frame.Labels) > 0 {
|
|
return frame.Labels
|
|
}
|
|
if frame.Label == "" {
|
|
return nil
|
|
}
|
|
return []string{frame.Label}
|
|
}
|
|
|
|
func mapBasicSolarEclipseModel(model basic.SolarEclipseRadiusModel) SolarEclipseRadiusModel {
|
|
switch model {
|
|
case basic.SolarEclipseModelIAUSingleK:
|
|
return SolarEclipseModelIAUSingleK
|
|
default:
|
|
return SolarEclipseModelNASABulletinSplitK
|
|
}
|
|
}
|
|
|
|
func mapBasicSolarEclipseType(eclipseType basic.SolarEclipseType) SolarEclipseType {
|
|
switch eclipseType {
|
|
case basic.SolarEclipsePartial:
|
|
return SolarEclipsePartial
|
|
case basic.SolarEclipseAnnular:
|
|
return SolarEclipseAnnular
|
|
case basic.SolarEclipseTotal:
|
|
return SolarEclipseTotal
|
|
case basic.SolarEclipseHybrid:
|
|
return SolarEclipseHybrid
|
|
default:
|
|
return SolarEclipseNone
|
|
}
|
|
}
|
|
|
|
func solarEclipseTTJDEToTime(ttJDE float64, location *time.Location) time.Time {
|
|
if ttJDE == 0 {
|
|
return time.Time{}
|
|
}
|
|
utcJDE := basic.TD2UT(ttJDE, false)
|
|
return basic.JDE2DateByZone(utcJDE, location, false)
|
|
}
|
|
|
|
func solarEclipseTimeToTTJDE(date time.Time) float64 {
|
|
utcJDE := basic.Date2JDE(date.UTC())
|
|
return basic.TD2UT(utcJDE, true)
|
|
}
|
|
|
|
func localSolarEclipseVisibilityThreshold(height, latitude float64) float64 {
|
|
if height <= 0 {
|
|
return 0
|
|
}
|
|
return -basic.HeightDegreeByLat(height, latitude)
|
|
}
|
|
|
|
func normalizeSolarEclipseDegree360(angle float64) float64 {
|
|
angle = math.Mod(angle, 360)
|
|
if angle < 0 {
|
|
angle += 360
|
|
}
|
|
return angle
|
|
}
|