- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息 - 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算 - 新增木星伽利略卫星位置、现象与接触事件计算 - 新增恒星星表、星座判定、自行修正与观测辅助能力 - 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包 - 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算 - 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试 - 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试 - 更新中文和英文 README,补充示例、精度说明、SVG 配图
371 lines
13 KiB
Go
371 lines
13 KiB
Go
package basic
|
|
|
|
import (
|
|
"math"
|
|
|
|
"b612.me/astro/planet"
|
|
. "b612.me/astro/tools"
|
|
)
|
|
|
|
const astronomicalUnitLightTimeDays = 0.0057755183
|
|
|
|
type planetPhysicalModel struct {
|
|
planetIndex int
|
|
positiveEast bool
|
|
equatorialRadius float64
|
|
polarRadius float64
|
|
poleRotation func(float64) (ra, dec, rotationEast float64)
|
|
}
|
|
|
|
type phaseAngleModel struct {
|
|
base float64
|
|
rate float64
|
|
accelRate float64
|
|
}
|
|
|
|
// PlanetPhysicalInfo 行星物理观测参数 / planetary physical observing parameters.
|
|
type PlanetPhysicalInfo struct {
|
|
// SubEarthLongitude 子地经度,单位度;正方向遵循该天体当前 IAU/Horizons 制图约定。
|
|
SubEarthLongitude float64
|
|
// SubEarthLatitude 子地纬度,单位度。
|
|
SubEarthLatitude float64
|
|
// SubSolarLongitude 子日经度,单位度;正方向遵循该天体当前 IAU/Horizons 制图约定。
|
|
SubSolarLongitude float64
|
|
// SubSolarLatitude 子日纬度,单位度。
|
|
SubSolarLatitude float64
|
|
// NorthPolePositionAngle 天体北极位置角,单位度。
|
|
NorthPolePositionAngle float64
|
|
}
|
|
|
|
var (
|
|
mercuryPhysicalModel = planetPhysicalModel{
|
|
planetIndex: 1,
|
|
positiveEast: false,
|
|
equatorialRadius: 2440.53,
|
|
polarRadius: 2438.26,
|
|
poleRotation: mercuryPoleRotation,
|
|
}
|
|
venusPhysicalModel = planetPhysicalModel{
|
|
planetIndex: 2,
|
|
positiveEast: true,
|
|
equatorialRadius: 6051.8,
|
|
polarRadius: 6051.8,
|
|
poleRotation: venusPoleRotation,
|
|
}
|
|
marsPhysicalModel = planetPhysicalModel{
|
|
planetIndex: 3,
|
|
positiveEast: false,
|
|
equatorialRadius: 3396.19,
|
|
polarRadius: 3376.20,
|
|
poleRotation: marsPoleRotation,
|
|
}
|
|
jupiterPhysicalModel = planetPhysicalModel{
|
|
planetIndex: 4,
|
|
positiveEast: false,
|
|
equatorialRadius: 71492.0,
|
|
polarRadius: 66854.0,
|
|
poleRotation: jupiterPoleRotation,
|
|
}
|
|
saturnPhysicalModel = planetPhysicalModel{
|
|
planetIndex: 5,
|
|
positiveEast: false,
|
|
equatorialRadius: 60268.0,
|
|
polarRadius: 54364.0,
|
|
poleRotation: saturnPoleRotation,
|
|
}
|
|
uranusPhysicalModel = planetPhysicalModel{
|
|
planetIndex: 6,
|
|
positiveEast: true,
|
|
equatorialRadius: 25559.0,
|
|
polarRadius: 24973.0,
|
|
poleRotation: uranusPoleRotation,
|
|
}
|
|
neptunePhysicalModel = planetPhysicalModel{
|
|
planetIndex: 7,
|
|
positiveEast: false,
|
|
equatorialRadius: 24764.0,
|
|
polarRadius: 24341.0,
|
|
poleRotation: neptunePoleRotation,
|
|
}
|
|
)
|
|
|
|
// MercuryPhysical 水星物理观测参数 / physical observing parameters of Mercury.
|
|
func MercuryPhysical(jd float64) PlanetPhysicalInfo {
|
|
return MercuryPhysicalN(jd, -1)
|
|
}
|
|
|
|
// MercuryPhysicalN 水星物理观测参数(截断版) / truncated physical observing parameters of Mercury.
|
|
func MercuryPhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, mercuryPhysicalModel)
|
|
}
|
|
|
|
// VenusPhysical 金星物理观测参数 / physical observing parameters of Venus.
|
|
func VenusPhysical(jd float64) PlanetPhysicalInfo {
|
|
return VenusPhysicalN(jd, -1)
|
|
}
|
|
|
|
// VenusPhysicalN 金星物理观测参数(截断版) / truncated physical observing parameters of Venus.
|
|
func VenusPhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, venusPhysicalModel)
|
|
}
|
|
|
|
// MarsPhysical 火星物理观测参数 / physical observing parameters of Mars.
|
|
func MarsPhysical(jd float64) PlanetPhysicalInfo {
|
|
return MarsPhysicalN(jd, -1)
|
|
}
|
|
|
|
// MarsPhysicalN 火星物理观测参数(截断版) / truncated physical observing parameters of Mars.
|
|
func MarsPhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, marsPhysicalModel)
|
|
}
|
|
|
|
// JupiterPhysical 木星物理观测参数 / physical observing parameters of Jupiter.
|
|
func JupiterPhysical(jd float64) PlanetPhysicalInfo {
|
|
return JupiterPhysicalN(jd, -1)
|
|
}
|
|
|
|
// JupiterPhysicalN 木星物理观测参数(截断版) / truncated physical observing parameters of Jupiter.
|
|
func JupiterPhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, jupiterPhysicalModel)
|
|
}
|
|
|
|
// SaturnPhysical 土星物理观测参数 / physical observing parameters of Saturn.
|
|
func SaturnPhysical(jd float64) PlanetPhysicalInfo {
|
|
return SaturnPhysicalN(jd, -1)
|
|
}
|
|
|
|
// SaturnPhysicalN 土星物理观测参数(截断版) / truncated physical observing parameters of Saturn.
|
|
func SaturnPhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, saturnPhysicalModel)
|
|
}
|
|
|
|
// UranusPhysical 天王星物理观测参数 / physical observing parameters of Uranus.
|
|
func UranusPhysical(jd float64) PlanetPhysicalInfo {
|
|
return UranusPhysicalN(jd, -1)
|
|
}
|
|
|
|
// UranusPhysicalN 天王星物理观测参数(截断版) / truncated physical observing parameters of Uranus.
|
|
func UranusPhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, uranusPhysicalModel)
|
|
}
|
|
|
|
// NeptunePhysical 海王星物理观测参数 / physical observing parameters of Neptune.
|
|
func NeptunePhysical(jd float64) PlanetPhysicalInfo {
|
|
return NeptunePhysicalN(jd, -1)
|
|
}
|
|
|
|
// NeptunePhysicalN 海王星物理观测参数(截断版) / truncated physical observing parameters of Neptune.
|
|
func NeptunePhysicalN(jd float64, n int) PlanetPhysicalInfo {
|
|
return planetPhysicalN(jd, n, neptunePhysicalModel)
|
|
}
|
|
|
|
func planetPhysicalN(jd float64, n int, model planetPhysicalModel) PlanetPhysicalInfo {
|
|
initialX, initialY, initialZ := planetXYZN(model.planetIndex, jd, n)
|
|
targetVector := Vector3{initialX, initialY, initialZ}
|
|
lightTimeDays := astronomicalUnitLightTimeDays * vectorMagnitude(targetVector)
|
|
targetJD := jd - lightTimeDays
|
|
|
|
geoX, geoY, geoZ := planetXYZN(model.planetIndex, targetJD, n)
|
|
geocentricVector := Vector3{geoX, geoY, geoZ}
|
|
observerDirection := normalizeVector(Vector3{-geocentricVector[0], -geocentricVector[1], -geocentricVector[2]})
|
|
|
|
heliocentricLongitude := planet.WherePlanetN(model.planetIndex, 0, targetJD, n)
|
|
heliocentricLatitude := planet.WherePlanetN(model.planetIndex, 1, targetJD, n)
|
|
solarDirection := normalizeVector(eclipticCartesian(heliocentricLongitude+180, -heliocentricLatitude, 1))
|
|
|
|
obliquity := EclipticObliquity(targetJD, false)
|
|
observerEquatorial := normalizeVector(rotateEclipticToEquatorial(observerDirection, obliquity))
|
|
solarEquatorial := normalizeVector(rotateEclipticToEquatorial(solarDirection, obliquity))
|
|
|
|
poleRA, poleDec, rotationEast := model.poleRotation(targetJD)
|
|
poleJ2000 := raDecToVector(poleRA, poleDec)
|
|
nodeJ2000 := Vector3{-math.Sin(poleRA * rad), math.Cos(poleRA * rad), 0}
|
|
eastJ2000 := normalizeVector(pxp(poleJ2000, nodeJ2000))
|
|
primeMeridianJ2000 := normalizeVector(Vector3{
|
|
nodeJ2000[0]*Cos(rotationEast) + eastJ2000[0]*Sin(rotationEast),
|
|
nodeJ2000[1]*Cos(rotationEast) + eastJ2000[1]*Sin(rotationEast),
|
|
nodeJ2000[2]*Cos(rotationEast) + eastJ2000[2]*Sin(rotationEast),
|
|
})
|
|
|
|
j2000ToDate := precessionMatrix(2451545.0, targetJD)
|
|
poleDate := normalizeVector(applyMatrix3(j2000ToDate, poleJ2000))
|
|
primeMeridianDate := normalizeVector(applyMatrix3(j2000ToDate, primeMeridianJ2000))
|
|
eastDate := normalizeVector(pxp(poleDate, primeMeridianDate))
|
|
|
|
poleRAOfDate, poleDecOfDate := vectorToRaDec(poleDate)
|
|
planetRA, planetDec := vectorToRaDec(observerEquatorial)
|
|
|
|
return PlanetPhysicalInfo{
|
|
SubEarthLongitude: bodyLongitude(observerEquatorial, primeMeridianDate, eastDate, model.positiveEast),
|
|
SubEarthLatitude: bodyLatitude(observerEquatorial, poleDate, model.equatorialRadius, model.polarRadius),
|
|
SubSolarLongitude: bodyLongitude(solarEquatorial, primeMeridianDate, eastDate, model.positiveEast),
|
|
SubSolarLatitude: bodyLatitude(solarEquatorial, poleDate, model.equatorialRadius, model.polarRadius),
|
|
NorthPolePositionAngle: northPolePositionAngle(planetRA, planetDec, poleRAOfDate, poleDecOfDate),
|
|
}
|
|
}
|
|
|
|
func bodyLongitude(direction, primeMeridian, eastAxis Vector3, positiveEast bool) float64 {
|
|
eastLongitude := Limit360(math.Atan2(vectorDot(direction, eastAxis), vectorDot(direction, primeMeridian)) * deg)
|
|
if positiveEast {
|
|
return eastLongitude
|
|
}
|
|
return Limit360(360 - eastLongitude)
|
|
}
|
|
|
|
func bodyLatitude(direction, pole Vector3, equatorialRadius, polarRadius float64) float64 {
|
|
geocentricLatitude := ArcSin(vectorDot(direction, pole))
|
|
if equatorialRadius == polarRadius {
|
|
return geocentricLatitude
|
|
}
|
|
ratio := (polarRadius * polarRadius) / (equatorialRadius * equatorialRadius)
|
|
return math.Atan2(math.Sin(geocentricLatitude*rad), math.Cos(geocentricLatitude*rad)*ratio) * deg
|
|
}
|
|
|
|
func northPolePositionAngle(planetRA, planetDec, poleRA, poleDec float64) float64 {
|
|
y := math.Cos(poleDec*rad) * math.Sin((poleRA-planetRA)*rad)
|
|
x := math.Sin(poleDec*rad)*math.Cos(planetDec*rad) -
|
|
math.Cos(poleDec*rad)*math.Sin(planetDec*rad)*math.Cos((poleRA-planetRA)*rad)
|
|
return Limit360(-math.Atan2(y, x) * deg)
|
|
}
|
|
|
|
func vectorDot(a, b Vector3) float64 {
|
|
return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
|
|
}
|
|
|
|
func vectorMagnitude(vector Vector3) float64 {
|
|
return math.Sqrt(vectorDot(vector, vector))
|
|
}
|
|
|
|
func normalizeVector(vector Vector3) Vector3 {
|
|
normalized, magnitude := pn(vector)
|
|
if magnitude == 0 {
|
|
return Vector3{}
|
|
}
|
|
return normalized
|
|
}
|
|
|
|
func mercuryPoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
julianCentury := days / 36525.0
|
|
m1 := Limit360(174.7910857 + 4.092335*days)
|
|
m2 := Limit360(349.5821714 + 8.184670*days)
|
|
m3 := Limit360(164.3732571 + 12.277005*days)
|
|
m4 := Limit360(339.1643429 + 16.369340*days)
|
|
m5 := Limit360(153.9554286 + 20.461675*days)
|
|
ra = 281.0103 - 0.0328*julianCentury
|
|
dec = 61.4155 - 0.0049*julianCentury
|
|
rotationEast = 329.5988 + 6.1385108*days +
|
|
0.01067257*Sin(m1) -
|
|
0.00112309*Sin(m2) -
|
|
0.00011040*Sin(m3) -
|
|
0.00002539*Sin(m4) -
|
|
0.00000571*Sin(m5)
|
|
return
|
|
}
|
|
|
|
func venusPoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
return 272.76, 67.16, 160.20 - 1.4813688*days
|
|
}
|
|
|
|
// Mars/Jupiter orientation terms follow the official NAIF text PCK `pck00011.tpc`.
|
|
var (
|
|
marsPoleRAAngles = [...]phaseAngleModel{
|
|
{198.991226, 19139.4819985, 0},
|
|
{226.292679, 38280.8511281, 0},
|
|
{249.663391, 57420.7251593, 0},
|
|
{266.183510, 76560.6367950, 0},
|
|
{79.398797, 0.5042615, 0},
|
|
}
|
|
marsPoleRACoefficients = [...]float64{0.000068, 0.000238, 0.000052, 0.000009, 0.419057}
|
|
marsPoleDECAngles = [...]phaseAngleModel{
|
|
{122.433576, 19139.9407476, 0},
|
|
{43.058401, 38280.8753272, 0},
|
|
{57.663379, 57420.7517205, 0},
|
|
{79.476401, 76560.6495004, 0},
|
|
{166.325722, 0.5042615, 0},
|
|
}
|
|
marsPoleDECCoefficients = [...]float64{0.000051, 0.000141, 0.000031, 0.000005, 1.591274}
|
|
marsPrimeMeridianAngles = [...]phaseAngleModel{
|
|
{129.071773, 19140.0328244, 0},
|
|
{36.352167, 38281.0473591, 0},
|
|
{56.668646, 57420.9295360, 0},
|
|
{67.364003, 76560.2552215, 0},
|
|
{104.792680, 95700.4387578, 0},
|
|
{95.391654, 0.5042615, 0},
|
|
}
|
|
marsPrimeMeridianCoefficients = [...]float64{0.000145, 0.000157, 0.000040, 0.000001, 0.000001, 0.584542}
|
|
jupiterPoleAngles = [...]phaseAngleModel{
|
|
{99.360714, 4850.4046, 0},
|
|
{175.895369, 1191.9605, 0},
|
|
{300.323162, 262.5475, 0},
|
|
{114.012305, 6070.2476, 0},
|
|
{49.511251, 64.3000, 0},
|
|
}
|
|
jupiterPoleRACoefficients = [...]float64{0.000117, 0.000938, 0.001432, 0.000030, 0.002150}
|
|
jupiterPoleDECCoefficients = [...]float64{0.000050, 0.000404, 0.000617, -0.000013, 0.000926}
|
|
)
|
|
|
|
func marsPoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
julianCentury := days / 36525.0
|
|
ra = 317.269202 - 0.10927547*julianCentury + sumSinOrientationTerms(marsPoleRAAngles[:], marsPoleRACoefficients[:], julianCentury)
|
|
dec = 54.432516 - 0.05827105*julianCentury + sumCosOrientationTerms(marsPoleDECAngles[:], marsPoleDECCoefficients[:], julianCentury)
|
|
rotationEast = 176.049863 + 350.891982443297*days +
|
|
sumSinOrientationTerms(marsPrimeMeridianAngles[:], marsPrimeMeridianCoefficients[:], julianCentury)
|
|
return
|
|
}
|
|
|
|
func jupiterPoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
julianCentury := days / 36525.0
|
|
ra = 268.056595 - 0.006499*julianCentury + sumSinOrientationTerms(jupiterPoleAngles[:], jupiterPoleRACoefficients[:], julianCentury)
|
|
dec = 64.495303 + 0.002413*julianCentury + sumCosOrientationTerms(jupiterPoleAngles[:], jupiterPoleDECCoefficients[:], julianCentury)
|
|
rotationEast = 284.95 + 870.5360000*days
|
|
return
|
|
}
|
|
|
|
func saturnPoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
julianCentury := days / 36525.0
|
|
ra = 40.589 - 0.036*julianCentury
|
|
dec = 83.537 - 0.004*julianCentury
|
|
rotationEast = 38.90 + 810.7939024*days
|
|
return
|
|
}
|
|
|
|
func uranusPoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
return 257.311, -15.175, 203.81 - 501.1600928*days
|
|
}
|
|
|
|
func neptunePoleRotation(jd float64) (ra, dec, rotationEast float64) {
|
|
days := jd - 2451545.0
|
|
julianCentury := days / 36525.0
|
|
n := Limit360(357.85 + 52.316*julianCentury)
|
|
ra = 299.36 + 0.70*Sin(n)
|
|
dec = 43.46 - 0.51*Cos(n)
|
|
rotationEast = 249.978 + 541.1397757*days - 0.48*Sin(n)
|
|
return
|
|
}
|
|
|
|
func (model phaseAngleModel) at(julianCentury float64) float64 {
|
|
return Limit360(model.base + model.rate*julianCentury + model.accelRate*julianCentury*julianCentury)
|
|
}
|
|
|
|
func sumSinOrientationTerms(angles []phaseAngleModel, coefficients []float64, julianCentury float64) float64 {
|
|
sum := 0.0
|
|
for i, angle := range angles {
|
|
sum += coefficients[i] * Sin(angle.at(julianCentury))
|
|
}
|
|
return sum
|
|
}
|
|
|
|
func sumCosOrientationTerms(angles []phaseAngleModel, coefficients []float64, julianCentury float64) float64 {
|
|
sum := 0.0
|
|
for i, angle := range angles {
|
|
sum += coefficients[i] * Cos(angle.at(julianCentury))
|
|
}
|
|
return sum
|
|
}
|