feat: 扩展天文计算能力
- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息 - 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算 - 新增木星伽利略卫星位置、现象与接触事件计算 - 新增恒星星表、星座判定、自行修正与观测辅助能力 - 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包 - 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算 - 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试 - 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试 - 更新中文和英文 README,补充示例、精度说明、SVG 配图
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user