feat: 扩展天文计算能力

- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息
- 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算
- 新增木星伽利略卫星位置、现象与接触事件计算
- 新增恒星星表、星座判定、自行修正与观测辅助能力
- 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包
- 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算
- 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试
- 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试
- 更新中文和英文 README,补充示例、精度说明、SVG 配图
This commit is contained in:
2026-05-01 22:38:44 +08:00
parent 98ff574495
commit 3ffdbe0034
365 changed files with 63589 additions and 17508 deletions
+29
View File
@@ -0,0 +1,29 @@
package sun
import (
"time"
"b612.me/astro/basic"
)
// Semidiameter 太阳视半径,单位角秒 / apparent solar semidiameter in arcseconds.
func Semidiameter(date time.Time) float64 {
return SemidiameterN(date, -1)
}
// SemidiameterN 太阳视半径(截断版),单位角秒 / truncated apparent solar semidiameter in arcseconds.
func SemidiameterN(date time.Time, n int) float64 {
jde := basic.Date2JDE(date.UTC())
return basic.SunSemidiameterN(basic.TD2UT(jde, true), n)
}
// Diameter 太阳视直径,单位角秒 / apparent solar diameter in arcseconds.
func Diameter(date time.Time) float64 {
return DiameterN(date, -1)
}
// DiameterN 太阳视直径(截断版),单位角秒 / truncated apparent solar diameter in arcseconds.
func DiameterN(date time.Time, n int) float64 {
jde := basic.Date2JDE(date.UTC())
return basic.SunDiameterN(basic.TD2UT(jde, true), n)
}
+75
View File
@@ -0,0 +1,75 @@
package sun
import (
"math"
"testing"
"time"
)
func TestObservationNFullMatchesDefault(t *testing.T) {
date := time.Date(2026, 4, 26, 9, 30, 45, 123456789, time.FixedZone("CST", 8*3600))
lon := 116.391
lat := 39.907
height := 45.0
twilightAngle := -6.0
assertSame := func(name string, got, want float64) {
t.Helper()
if math.Float64bits(got) != math.Float64bits(want) {
t.Fatalf("%s full-n mismatch", name)
}
}
assertTimeSame := func(name string, got, want time.Time) {
t.Helper()
if got.UnixNano() != want.UnixNano() || got.Location().String() != want.Location().String() {
t.Fatalf("%s full-n mismatch", name)
}
}
assertErrSame := func(name string, got, want error) {
t.Helper()
switch {
case got == nil && want == nil:
return
case got == nil || want == nil:
t.Fatalf("%s full-n mismatch", name)
case got.Error() != want.Error():
t.Fatalf("%s full-n mismatch", name)
}
}
assertSame("HourAngle", HourAngle(date, lon, lat), HourAngleN(date, lon, lat, -1))
assertSame("ParallacticAngle", ParallacticAngle(date, lon, lat), ParallacticAngleN(date, lon, lat, -1))
assertSame("Azimuth", Azimuth(date, lon, lat), AzimuthN(date, lon, lat, -1))
assertSame("Altitude", Altitude(date, lon, lat), AltitudeN(date, lon, lat, -1))
assertSame("Zenith", Zenith(date, lon, lat), ZenithN(date, lon, lat, -1))
if !math.IsNaN(Altitude(date, lon, lat)) && math.Abs((Altitude(date, lon, lat)+Zenith(date, lon, lat))-90) > 1e-12 {
t.Fatal("sun altitude + zenith should equal 90 degrees")
}
assertTimeSame("CulminationTime", CulminationTime(date, lon), CulminationTimeN(date, lon, -1))
assertTimeSame("ApparentSolarTime", ApparentSolarTime(date, lon), ApparentSolarTimeN(date, lon, -1))
rise1, err1 := RiseTime(date, lon, lat, height, true)
rise2, err2 := RiseTimeN(date, lon, lat, height, true, -1)
assertTimeSame("RiseTime", rise1, rise2)
assertErrSame("RiseTime.err", err1, err2)
set1, err1 := SetTime(date, lon, lat, height, true)
set2, err2 := SetTimeN(date, lon, lat, height, true, -1)
assertTimeSame("SetTime", set1, set2)
assertErrSame("SetTime.err", err1, err2)
down1, err1 := DownTime(date, lon, lat, height, true)
down2, err2 := DownTimeN(date, lon, lat, height, true, -1)
assertTimeSame("DownTime", down1, down2)
assertErrSame("DownTime.err", err1, err2)
morning1, err1 := MorningTwilight(date, lon, lat, twilightAngle)
morning2, err2 := MorningTwilightN(date, lon, lat, twilightAngle, -1)
assertTimeSame("MorningTwilight", morning1, morning2)
assertErrSame("MorningTwilight.err", err1, err2)
evening1, err1 := EveningTwilight(date, lon, lat, twilightAngle)
evening2, err2 := EveningTwilightN(date, lon, lat, twilightAngle, -1)
assertTimeSame("EveningTwilight", evening1, evening2)
assertErrSame("EveningTwilight.err", err1, err2)
}
+24
View File
@@ -0,0 +1,24 @@
package sun
import (
"time"
"b612.me/astro/basic"
)
// ParallacticAngle 太阳视差角(天顶方向角) / solar parallactic angle.
//
// lon/lat 为观测者经纬度,东正西负、北正南负;返回值为有符号视差角,单位度。
func ParallacticAngle(date time.Time, lon, lat float64) float64 {
return basic.ParallacticAngleByHourAngle(HourAngle(date, lon, lat), ApparentDec(date), lat)
}
// ParallacticAngleN 截断项太阳视差角(天顶方向角) / truncated solar parallactic angle.
func ParallacticAngleN(date time.Time, lon, lat float64, n int) float64 {
jde := basic.Date2JDE(date.UTC())
return basic.ParallacticAngleByHourAngle(
HourAngleN(date, lon, lat, n),
basic.HSunApparentDecN(basic.TD2UT(jde, true), n),
lat,
)
}
+33
View File
@@ -0,0 +1,33 @@
package sun
import (
"time"
"b612.me/astro/basic"
)
// PhysicalInfo 太阳物理观测参数 / physical observing parameters of the Sun.
type PhysicalInfo struct {
// P 太阳北极位置角,单位度 / position angle of the solar north pole in degrees.
P float64
// B0 日面中心太阳纬度,单位度 / heliographic latitude of the disk center in degrees.
B0 float64
// L0 日面中心卡林顿经度,单位度 / Carrington heliographic longitude of the disk center in degrees.
L0 float64
}
// Physical 太阳物理观测参数 / physical observing parameters of the Sun.
func Physical(date time.Time) PhysicalInfo {
return PhysicalN(date, -1)
}
// PhysicalN 太阳物理观测参数(截断版) / truncated physical observing parameters of the Sun.
func PhysicalN(date time.Time, n int) PhysicalInfo {
jde := basic.Date2JDE(date.UTC())
info := basic.SunPhysicalN(basic.TD2UT(jde, true), n)
return PhysicalInfo{
P: info.P,
B0: info.B0,
L0: info.L0,
}
}
+33
View File
@@ -0,0 +1,33 @@
package sun
import (
"math"
"testing"
"time"
"b612.me/astro/basic"
)
func TestPhysicalWrapperMatchesBasic(t *testing.T) {
date := time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC)
jde := basic.Date2JDE(date.UTC())
got := Physical(date)
gotN := PhysicalN(date, -1)
want := basic.SunPhysicalN(basic.TD2UT(jde, true), -1)
assertSamePhysicalFloat(t, "P", got.P, want.P)
assertSamePhysicalFloat(t, "B0", got.B0, want.B0)
assertSamePhysicalFloat(t, "L0", got.L0, want.L0)
assertSamePhysicalFloat(t, "PhysicalN.P", got.P, gotN.P)
assertSamePhysicalFloat(t, "PhysicalN.B0", got.B0, gotN.B0)
assertSamePhysicalFloat(t, "PhysicalN.L0", got.L0, gotN.L0)
}
func assertSamePhysicalFloat(t *testing.T, name string, got, want float64) {
t.Helper()
if math.Float64bits(got) != math.Float64bits(want) {
t.Fatalf("%s mismatch: got %.18f want %.18f", name, got, want)
}
}
+21
View File
@@ -0,0 +1,21 @@
package sun
import (
"math"
"testing"
"time"
)
func TestPhysicalPreservesInstantAcrossTimezones(t *testing.T) {
utc := time.Date(2026, 4, 28, 9, 30, 45, 123000000, time.UTC)
shanghai := utc.In(time.FixedZone("UTC+8", 8*3600))
got := Physical(shanghai)
want := Physical(utc)
valuesGot := []float64{got.P, got.B0, got.L0}
valuesWant := []float64{want.P, want.B0, want.L0}
for i := range valuesGot {
if math.Float64bits(valuesGot[i]) != math.Float64bits(valuesWant[i]) {
t.Fatalf("timezone instant mismatch at index %d: got %.18f want %.18f", i, valuesGot[i], valuesWant[i])
}
}
}
+27
View File
@@ -0,0 +1,27 @@
package sun
import (
"time"
"b612.me/astro/basic"
)
// ApparentAltitude 太阳视高度角 / apparent solar altitude.
func ApparentAltitude(date time.Time, lon, lat, pressureHPa, temperatureC float64) float64 {
return basic.ApparentAltitude(Altitude(date, lon, lat), pressureHPa, temperatureC)
}
// ApparentZenith 太阳视天顶距 / apparent solar zenith distance.
func ApparentZenith(date time.Time, lon, lat, pressureHPa, temperatureC float64) float64 {
return 90 - ApparentAltitude(date, lon, lat, pressureHPa, temperatureC)
}
// ApparentAltitudeN 太阳视高度角(截断版) / truncated apparent solar altitude.
func ApparentAltitudeN(date time.Time, lon, lat, pressureHPa, temperatureC float64, n int) float64 {
return basic.ApparentAltitude(AltitudeN(date, lon, lat, n), pressureHPa, temperatureC)
}
// ApparentZenithN 太阳视天顶距(截断版) / truncated apparent solar zenith distance.
func ApparentZenithN(date time.Time, lon, lat, pressureHPa, temperatureC float64, n int) float64 {
return 90 - ApparentAltitudeN(date, lon, lat, pressureHPa, temperatureC, n)
}
+30
View File
@@ -0,0 +1,30 @@
package sun
import (
"math"
"testing"
"time"
"b612.me/astro/basic"
)
func TestApparentAltitudeWrappers(t *testing.T) {
date := time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC)
pressureHPa := 1010.0
temperatureC := 10.0
trueAltitude := Altitude(date, 115, 40)
assertSunRefractionClose(t, "ApparentAltitude", ApparentAltitude(date, 115, 40, pressureHPa, temperatureC), basic.ApparentAltitude(trueAltitude, pressureHPa, temperatureC), 1e-12)
assertSunRefractionClose(t, "ApparentZenith", ApparentZenith(date, 115, 40, pressureHPa, temperatureC), 90-ApparentAltitude(date, 115, 40, pressureHPa, temperatureC), 1e-12)
trueAltitudeN := AltitudeN(date, 115, 40, -1)
assertSunRefractionClose(t, "ApparentAltitudeN", ApparentAltitudeN(date, 115, 40, pressureHPa, temperatureC, -1), basic.ApparentAltitude(trueAltitudeN, pressureHPa, temperatureC), 1e-12)
assertSunRefractionClose(t, "ApparentZenithN", ApparentZenithN(date, 115, 40, pressureHPa, temperatureC, -1), 90-ApparentAltitudeN(date, 115, 40, pressureHPa, temperatureC, -1), 1e-12)
}
func assertSunRefractionClose(t *testing.T, name string, got, want, tolerance float64) {
t.Helper()
if math.Abs(got-want) > tolerance {
t.Fatalf("%s mismatch: got %.18f want %.18f", name, got, want)
}
}
+320 -110
View File
@@ -16,6 +16,27 @@ var (
ERR_TWILIGHT_NOT_EXISTS = errors.New("ERROR:今日晨昏朦影不存在!")
)
func riseSetResult(date time.Time, jde float64, err error) (time.Time, error) {
if err != nil {
switch {
case errors.Is(err, basic.ErrNeverRise):
return time.Time{}, ERR_SUN_NEVER_RISE
case errors.Is(err, basic.ErrNeverSet):
return time.Time{}, ERR_SUN_NEVER_SET
default:
return time.Time{}, err
}
}
return basic.JDE2DateByZone(jde, date.Location(), true), nil
}
func twilightResult(date time.Time, jde float64, err error) (time.Time, error) {
if err != nil {
return time.Time{}, ERR_TWILIGHT_NOT_EXISTS
}
return basic.JDE2DateByZone(jde, date.Location(), true), nil
}
/*
太阳
视星等 26.74
@@ -25,14 +46,13 @@ var (
角直径 31.6 32.7
*/
// RiseTime 太阳升起时间
// date,取日期,时区忽略
// lon,经度,东正西负
// lat,纬度,北正南负
// height,高度
// aerotrue时进行大气修正
// RiseTime 日出时刻 / sunrise time.
//
// date 取其所在时区的当地日期,返回值保持相同时区;lon/lat 为观测者经纬度,东正西负、北正南负;
// height 为海拔高度,单位米;aero 为 true 时加入标准大气折射。
// date is interpreted on its local civil day and the result keeps the same time zone. lon/lat are east-positive and north-positive;
// height is observer elevation in meters, and aero enables standard atmospheric refraction.
func RiseTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, error) {
var err error
var aeroFloat float64
if aero {
aeroFloat = 1
@@ -40,40 +60,57 @@ func RiseTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, e
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
//忽略时区的字面量时间
// 以 date 的当地日期为锚点,并读取其时区偏移参与地方时计算。
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
//risedate 时区修正后的时间,转换应当包括时区
riseJde := basic.GetSunRiseTime(jde, lon, lat, timezone, aeroFloat, height)
if riseJde == -2 {
err = ERR_SUN_NEVER_RISE
}
if riseJde == -1 {
err = ERR_SUN_NEVER_SET
}
return basic.JDE2DateByZone(riseJde, date.Location(), true), err
// 返回值保持与输入 date 一致的时区。
riseJde, err := basic.GetSunRiseTime(jde, lon, lat, timezone, aeroFloat, height)
return riseSetResult(date, riseJde, err)
}
// deprecated: -- use SetTime instead
// DownTime 太阳落下时间
// date,当地时区日期,务必做时区修正
// lon,经度,东正西负
// lat,纬度,北正南负
// height,高度
// aerotrue时进行大气修正
// RiseTimeN 截断项日出时刻 / truncated sunrise time.
//
// 参数与 RiseTime 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as RiseTime. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func RiseTimeN(date time.Time, lon, lat, height float64, aero bool, n int) (time.Time, error) {
var aeroFloat float64
if aero {
aeroFloat = 1
}
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
riseJde, err := basic.GetSunRiseTimeN(jde, lon, lat, timezone, aeroFloat, height, n)
return riseSetResult(date, riseJde, err)
}
// DownTime 日落时刻别名 / deprecated sunset alias.
//
// Deprecated: use SetTime instead.
//
// 参数与 SetTime 相同,仅为兼容旧接口保留。
// Same as SetTime and kept only for backward compatibility.
func DownTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, error) {
return SetTime(date, lon, lat, height, aero)
}
// SetTime 太阳落下时间
// date,当地时区日期,务必做时区修正
// lon,经度,东正西负
// lat,纬度,北正南负
// height,高度
// aerotrue时进行大气修正
// DownTimeN 截断项日落时刻别名 / deprecated truncated sunset alias.
//
// 参数与 SetTimeN 相同,仅为兼容旧接口保留。
// Same as SetTimeN and kept only for backward compatibility.
func DownTimeN(date time.Time, lon, lat, height float64, aero bool, n int) (time.Time, error) {
return SetTimeN(date, lon, lat, height, aero, n)
}
// SetTime 日落时刻 / sunset time.
//
// 参数与 RiseTime 相同,返回给定当地日期内的日落时刻。
// Uses the same inputs as RiseTime and returns the sunset time on the corresponding local civil day.
func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, error) {
var err error
var aeroFloat float64
if aero {
aeroFloat = 1
@@ -84,46 +121,66 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
downJde := basic.GetSunSetTime(jde, lon, lat, timezone, aeroFloat, height)
if downJde == -2 {
err = ERR_SUN_NEVER_RISE
}
if downJde == -1 {
err = ERR_SUN_NEVER_SET
}
return basic.JDE2DateByZone(downJde, date.Location(), true), err
downJde, err := basic.GetSunSetTime(jde, lon, lat, timezone, aeroFloat, height)
return riseSetResult(date, downJde, err)
}
// MorningTwilight 晨朦影
// date,当地时区日期,返回的时间时区与此参数中的时区一致
// lon,经度,东正西负
// lat,纬度,北正南负
// angle,朦影角度:可选-6 -12 -18(民用、航海、天文)
func MorningTwilight(date time.Time, lon, lat, angle float64) (time.Time, error) {
var err error
// SetTimeN 截断项日落时刻 / truncated sunset time.
//
// 参数与 RiseTimeN 相同,返回给定当地日期内的日落时刻。
// Uses the same inputs as RiseTimeN and returns the sunset time on the corresponding local civil day.
func SetTimeN(date time.Time, lon, lat, height float64, aero bool, n int) (time.Time, error) {
var aeroFloat float64
if aero {
aeroFloat = 1
}
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
calcJde := basic.MorningTwilight(jde, lon, lat, timezone, angle)
if calcJde == -2 {
err = ERR_TWILIGHT_NOT_EXISTS
}
if calcJde == -1 {
err = ERR_TWILIGHT_NOT_EXISTS
}
return basic.JDE2DateByZone(calcJde, date.Location(), true), err
downJde, err := basic.GetSunSetTimeN(jde, lon, lat, timezone, aeroFloat, height, n)
return riseSetResult(date, downJde, err)
}
// EveningTwilight 昏朦影
// date,当地时区日期,返回的时间时区与此参数中的时区一致
// lon,经度,东正西负
// lat,纬度,北正南负
// angle,朦影角度:可选-6 -12 -18(民用、航海、天文)
// MorningTwilight 晨光始时 / morning twilight.
//
// date 取其所在时区的当地日期,返回值保持相同时区;lon/lat 为观测者经纬度,东正西负、北正南负;
// angle 为目标太阳高度角,常用 -6/-12/-18 度,分别对应民用、航海、天文朦影。
// date is interpreted on its local civil day and the result keeps the same time zone. lon/lat are east-positive and north-positive;
// angle is the target solar altitude in degrees, typically -6, -12, or -18 for civil, nautical, and astronomical twilight.
func MorningTwilight(date time.Time, lon, lat, angle float64) (time.Time, error) {
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
calcJde, err := basic.MorningTwilight(jde, lon, lat, timezone, angle)
return twilightResult(date, calcJde, err)
}
// MorningTwilightN 截断项晨光始时 / truncated morning twilight.
//
// 参数与 MorningTwilight 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as MorningTwilight. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func MorningTwilightN(date time.Time, lon, lat, angle float64, n int) (time.Time, error) {
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
calcJde, err := basic.MorningTwilightN(jde, lon, lat, timezone, angle, n)
return twilightResult(date, calcJde, err)
}
// EveningTwilight 暮光终时 / evening twilight.
//
// 参数与 MorningTwilight 相同,返回对应当地日期的暮光结束时刻。
// Uses the same inputs as MorningTwilight and returns the evening-twilight time on the corresponding local civil day.
func EveningTwilight(date time.Time, lon, lat, angle float64) (time.Time, error) {
var err error
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
@@ -131,18 +188,29 @@ func EveningTwilight(date time.Time, lon, lat, angle float64) (time.Time, error)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
//不需要进行力学时转换,会在GetBanTime中转换
calcJde := basic.EveningTwilight(jde, lon, lat, timezone, angle)
if calcJde == -2 {
err = ERR_TWILIGHT_NOT_EXISTS
}
if calcJde == -1 {
err = ERR_TWILIGHT_NOT_EXISTS
}
return basic.JDE2DateByZone(calcJde, date.Location(), true), err
calcJde, err := basic.EveningTwilight(jde, lon, lat, timezone, angle)
return twilightResult(date, calcJde, err)
}
// EclipticObliquity 黄赤交角
// 返回date对应UTC世界时的黄赤交角,nutation为true时,计算交角章动
// EveningTwilightN 截断项暮光终时 / truncated evening twilight.
//
// 参数与 MorningTwilightN 相同,返回对应当地日期的暮光结束时刻。
// Uses the same inputs as MorningTwilightN and returns the evening-twilight time on the corresponding local civil day.
func EveningTwilightN(date time.Time, lon, lat, angle float64, n int) (time.Time, error) {
if date.Hour() > 12 {
date = date.Add(time.Hour * -12)
}
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
calcJde, err := basic.EveningTwilightN(jde, lon, lat, timezone, angle, n)
return twilightResult(date, calcJde, err)
}
// EclipticObliquity 黄赤交角 / ecliptic obliquity.
//
// 返回 date 对应绝对时刻的黄赤交角,单位度;nutation 为 true 时加入交角章动。
// Returns the obliquity of the ecliptic at the instant represented by date, in degrees. When nutation is true, obliquity nutation is included.
func EclipticObliquity(date time.Time, nutation bool) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
@@ -152,8 +220,10 @@ func EclipticObliquity(date time.Time, nutation bool) float64 {
return basic.EclipticObliquity(jde, nutation)
}
// EclipticNutation 黄经章动(2000b)
// 返回date对应UTC世界时的黄经章动
// EclipticNutation 黄经章动IAU 2000B / nutation in longitude, IAU 2000B.
//
// 返回 date 对应绝对时刻的黄经章动,单位度。
// Returns nutation in longitude at the instant represented by date, in degrees.
func EclipticNutation(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
@@ -161,17 +231,21 @@ func EclipticNutation(date time.Time) float64 {
return basic.Nutation2000Bi(basic.TD2UT(jde, true))
}
// EclipticNutation1980 黄经章动(iau 1980)
// 返回date对应UTC世界时的黄经章动
// EclipticNutation1980 黄经章动IAU 1980 / nutation in longitude, IAU 1980.
//
// 返回 date 对应绝对时刻的黄经章动,单位度。
// Returns nutation in longitude at the instant represented by date, in degrees.
func EclipticNutation1980(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
//进行力学时转换与章动计算
return basic.Nutation2000Bi(basic.TD2UT(jde, true))
return basic.Nutation1980i(basic.TD2UT(jde, true))
}
// AxialtiltNutation 交角章动(2000b)
// 返回date对应UTC世界时的交角章动
// AxialtiltNutation 交角章动IAU 2000B / nutation in obliquity, IAU 2000B.
//
// 返回 date 对应绝对时刻的交角章动,单位度。
// Returns nutation in obliquity at the instant represented by date, in degrees.
func AxialtiltNutation(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
@@ -179,8 +253,10 @@ func AxialtiltNutation(date time.Time) float64 {
return basic.Nutation2000Bs(basic.TD2UT(jde, true))
}
// AxialtiltNutation1980 交角章动(1980)
// 返回date对应UTC世界时的交角章动
// AxialtiltNutation1980 交角章动IAU 1980 / nutation in obliquity, IAU 1980.
//
// 返回 date 对应绝对时刻的交角章动,单位度。
// Returns nutation in obliquity at the instant represented by date, in degrees.
func AxialtiltNutation1980(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
@@ -188,79 +264,120 @@ func AxialtiltNutation1980(date time.Time) float64 {
return basic.Nutation1980s(basic.TD2UT(jde, true))
}
// GeometricLo 太阳几何黄经
// 返回date对应UTC世界时的太阳几何黄经
// GeometricLo 太阳几何黄经 / geometric ecliptic longitude.
//
// 返回 date 对应绝对时刻的太阳几何黄经,单位度。
// Returns the Sun's geometric ecliptic longitude at the instant represented by date, in degrees.
func GeometricLo(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.SunLo(basic.TD2UT(jde, true))
}
// TrueLo 太阳真黄经
// 返回date对应UTC世界时的太阳真黄经
// TrueLo 太阳真黄经 / true ecliptic longitude.
//
// 返回 date 对应绝对时刻的太阳真黄经,单位度。
// Returns the Sun's true ecliptic longitude at the instant represented by date, in degrees.
func TrueLo(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.HSunTrueLo(basic.TD2UT(jde, true))
}
// TrueBo 太阳真黄纬
// 返回date对应UTC世界时的太阳真黄纬
// TrueLoN 截断项太阳真黄经 / truncated true ecliptic longitude.
//
// 参数与 TrueLo 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as TrueLo. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func TrueLoN(date time.Time, n int) float64 {
jde := basic.Date2JDE(date.UTC())
return basic.HSunTrueLoN(basic.TD2UT(jde, true), n)
}
// TrueBo 太阳真黄纬 / true ecliptic latitude.
//
// 返回 date 对应绝对时刻的太阳真黄纬,单位度。
// Returns the Sun's true ecliptic latitude at the instant represented by date, in degrees.
func TrueBo(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.HSunTrueBo(basic.TD2UT(jde, true))
}
// ApparentLo 太阳视黄经
// 返回date对应UTC世界时的太阳视黄经
// TrueBoN 截断项太阳真黄纬 / truncated true ecliptic latitude.
//
// 参数与 TrueBo 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as TrueBo. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func TrueBoN(date time.Time, n int) float64 {
jde := basic.Date2JDE(date.UTC())
return basic.HSunTrueBoN(basic.TD2UT(jde, true), n)
}
// ApparentLo 太阳视黄经 / apparent ecliptic longitude.
//
// 返回 date 对应绝对时刻的太阳视黄经,单位度。
// Returns the Sun's apparent ecliptic longitude at the instant represented by date, in degrees.
func ApparentLo(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.HSunApparentLo(basic.TD2UT(jde, true))
}
// ApparentRa 太阳地心视赤经
// 返回date对应UTC世界时的太阳视赤经(使用黄道坐标转换,且默认忽略黄纬)
// ApparentRa 太阳地心视赤经 / apparent geocentric right ascension.
//
// 返回 date 对应绝对时刻的太阳地心视赤经,单位度。
// Returns the Sun's apparent geocentric right ascension at the instant represented by date, in degrees.
func ApparentRa(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.HSunApparentRa(basic.TD2UT(jde, true))
}
// ApparentDec 太阳地心视赤纬
// 返回date对应UTC世界时的太阳视赤纬(使用黄道坐标转换,且默认忽略黄纬)
// ApparentDec 太阳地心视赤纬 / apparent geocentric declination.
//
// 返回 date 对应绝对时刻的太阳地心视赤纬,单位度。
// Returns the Sun's apparent geocentric declination at the instant represented by date, in degrees.
func ApparentDec(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.HSunApparentDec(basic.TD2UT(jde, true))
}
// ApparentRaDec 太阳地心视赤经和赤纬
// 返回date对应UTC世界时的太阳视赤纬(使用黄道坐标转换,且默认忽略黄纬)
// ApparentRaDec 太阳地心视赤经、视赤纬 / apparent geocentric right ascension and declination.
//
// 返回 date 对应绝对时刻的太阳地心视赤经与视赤纬,单位度。
// Returns the Sun's apparent geocentric right ascension and declination at the instant represented by date, in degrees.
func ApparentRaDec(date time.Time) (float64, float64) {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.HSunApparentRaDec(basic.TD2UT(jde, true))
}
// MidFunc 太阳中间方程
// MidFunc 太阳中心差 / solar equation of center.
//
// 返回 date 对应绝对时刻的太阳中心差,单位度。
// Returns the Sun's equation of center at the instant represented by date, in degrees.
func MidFunc(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.SunMidFun(basic.TD2UT(jde, true))
}
// EquationTime 均时差
// 返回date对应UTC世界时的均时差
// EquationTime 均时差 / equation of time.
//
// 返回 date 对应绝对时刻的均时差,单位小时。
// Returns the equation of time at the instant represented by date, in hours.
func EquationTime(date time.Time) float64 {
//转换为UTC时间
jde := basic.Date2JDE(date.UTC())
return basic.SunTime(basic.TD2UT(jde, true))
}
// HourAngle 太阳时角
// 返回给定经纬度、对应date时区date时刻的太阳时角(
// HourAngle 太阳时角 / hour angle.
//
// date 为观测时刻,会读取其时区参与地方时计算;lon 为观测者经度,东正西负;返回值单位度。
// lat 目前不参与计算,仅为与其他观测接口保持参数形状一致。
// date is the observing instant and its zone offset participates in local-time calculations. lon is east-positive longitude and the result is in degrees.
// lat is currently unused and kept only for API symmetry with the other observation helpers.
func HourAngle(date time.Time, lon, lat float64) float64 {
jde := basic.Date2JDE(date)
_, loc := date.Zone()
@@ -268,26 +385,81 @@ func HourAngle(date time.Time, lon, lat float64) float64 {
return basic.SunTimeAngle(jde, lon, lat, timezone)
}
// Azimuth 太阳方位角
// 返回给定经纬度、对应date时区date时刻的太阳方位角(正北为0,向东增加)
// HourAngleN 截断项太阳时角 / truncated hour angle.
//
// 参数与 HourAngle 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as HourAngle. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func HourAngleN(date time.Time, lon, lat float64, n int) float64 {
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
return basic.SunTimeAngleN(jde, lon, lat, timezone, n)
}
// Azimuth 太阳方位角 / azimuth.
//
// date 为观测时刻,会读取其时区参与地方时计算;lon/lat 为观测者经纬度,东正西负、北正南负;返回值按正北为 0°、向东增加。
// date is the observing instant and its zone offset participates in local-time calculations. lon/lat are east-positive and north-positive; azimuth is measured from north toward east.
func Azimuth(date time.Time, lon, lat float64) float64 {
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
return basic.SunAngle(jde, lon, lat, timezone)
return basic.SunAzimuth(jde, lon, lat, timezone)
}
// Zenith 太阳高度角
// 返回给定经纬度、对应date时区date时刻的太阳高度角
func Zenith(date time.Time, lon, lat float64) float64 {
// AzimuthN 截断项太阳方位角 / truncated azimuth.
//
// 参数与 Azimuth 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as Azimuth. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func AzimuthN(date time.Time, lon, lat float64, n int) float64 {
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
return basic.SunAzimuthN(jde, lon, lat, timezone, n)
}
// Altitude 太阳高度角 / solar altitude.
//
// date 为观测时刻,会读取其时区参与地方时计算;lon/lat 为观测者经纬度,东正西负、北正南负;返回值单位度。
// date is the observing instant and its zone offset participates in local-time calculations. lon/lat are east-positive and north-positive; the result is in degrees.
func Altitude(date time.Time, lon, lat float64) float64 {
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
return basic.SunHeight(jde, lon, lat, timezone)
}
// CulminationTime 太阳中天时间
// 返回给定经纬度、对应date时区date时刻的太阳中天日期
// Zenith 太阳天顶距 / solar zenith distance.
//
// 参数与 Altitude 相同,返回值为对应时刻的天顶距,单位度。
// Uses the same inputs as Altitude and returns the zenith distance in degrees.
func Zenith(date time.Time, lon, lat float64) float64 {
return 90 - Altitude(date, lon, lat)
}
// AltitudeN 截断项太阳高度角 / truncated solar altitude.
//
// 参数与 Altitude 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as Altitude. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func AltitudeN(date time.Time, lon, lat float64, n int) float64 {
jde := basic.Date2JDE(date)
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
return basic.SunHeightN(jde, lon, lat, timezone, n)
}
// ZenithN 截断项太阳天顶距 / truncated solar zenith distance.
//
// 参数与 AltitudeN 相同,返回值为对应时刻的天顶距,单位度。
// Uses the same inputs as AltitudeN and returns the zenith distance in degrees.
func ZenithN(date time.Time, lon, lat float64, n int) float64 {
return 90 - AltitudeN(date, lon, lat, n)
}
// CulminationTime 太阳中天时刻 / culmination time.
//
// date 取其所在时区的当地日期,返回值保持相同时区;lon 为观测者经度,东正西负。
// date is interpreted on its local civil day and the result keeps the same time zone. lon is east-positive longitude.
func CulminationTime(date time.Time, lon float64) time.Time {
jde := basic.Date2JDE(date.Add(time.Duration(-1*date.Hour())*time.Hour)) + 0.5
_, loc := date.Zone()
@@ -296,16 +468,32 @@ func CulminationTime(date time.Time, lon float64) time.Time {
return basic.JDE2DateByZone(calcJde, date.Location(), false)
}
// EarthDistance 日地距离
// 返回date对应UTC世界时日地距离
// CulminationTimeN 截断项太阳中天时刻 / truncated culmination time.
//
// 参数与 CulminationTime 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as CulminationTime. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func CulminationTimeN(date time.Time, lon float64, n int) time.Time {
jde := basic.Date2JDE(date.Add(time.Duration(-1*date.Hour())*time.Hour)) + 0.5
_, loc := date.Zone()
timezone := float64(loc) / 3600.0
calcJde := basic.CulminationTimeN(jde, lon, timezone, n) - timezone/24.00
return basic.JDE2DateByZone(calcJde, date.Location(), false)
}
// EarthDistance 日地距离 / Earth-Sun distance.
//
// 返回 date 对应绝对时刻的日地距离,单位 AU。
// Returns the Earth-Sun distance at the instant represented by date, in astronomical units.
func EarthDistance(date time.Time) float64 {
jde := basic.Date2JDE(date.UTC())
jde = basic.TD2UT(jde, true)
return basic.EarthAway(jde)
}
// ApparentSolarTime 真太阳时
// 返回给定经度lon的真太阳时
// ApparentSolarTime 真太阳时 / apparent solar time.
//
// 返回 date 这一绝对时刻在给定经度 lon 处对应的真太阳时,结果时区为按经度换算的地方平太阳时区。
// Returns the apparent solar time for the instant represented by date at longitude lon. The result uses a synthetic local-solar time zone derived from longitude.
func ApparentSolarTime(date time.Time, lon float64) time.Time {
//真太阳时=太阳时角+12小时
trueTime := (HourAngle(date, lon, 0) + 180) / 15
@@ -327,3 +515,25 @@ func ApparentSolarTime(date time.Time, lon float64) time.Time {
int(trueTime), int(minute), int(second), int((second-math.Floor(second))*1000000000),
time.FixedZone("LTZ", int(lon*3600.00/15.0)))
}
// ApparentSolarTimeN 截断项真太阳时 / truncated apparent solar time.
//
// 参数与 ApparentSolarTime 相同;n<0 使用当前仓库内嵌的全部 VSOP 项,其余值用于截断太阳位置级数。
// Uses the same inputs as ApparentSolarTime. n<0 keeps all embedded VSOP terms in this repository; other values truncate the solar series.
func ApparentSolarTimeN(date time.Time, lon float64, n int) time.Time {
trueTime := (HourAngleN(date, lon, 0, n) + 180) / 15
if trueTime > 24 {
trueTime -= 24
}
minute := (trueTime - math.Floor(trueTime)) * 60
second := (minute - math.Floor(minute)) * 60
trueSunTime := date.In(time.FixedZone("LTZ", int(lon*3600.00/15.0)))
if trueSunTime.Hour()-int(trueTime) > 12 {
trueSunTime = trueSunTime.Add(time.Hour * 24)
} else if int(trueTime)-trueSunTime.Hour() > 12 {
trueSunTime = trueSunTime.Add(-time.Hour * 24)
}
return time.Date(trueSunTime.Year(), trueSunTime.Month(), trueSunTime.Day(),
int(trueTime), int(minute), int(second), int((second-math.Floor(second))*1000000000),
time.FixedZone("LTZ", int(lon*3600.00/15.0)))
}
-5
View File
@@ -1,7 +1,6 @@
package sun
import (
"fmt"
"math"
"testing"
"time"
@@ -27,8 +26,4 @@ func TestSun(t *testing.T) {
if math.Abs(bo) > 2 {
t.Fatal(bo)
}
fmt.Println(CulminationTime(now, 115))
fmt.Println(SetTime(now, 115, 40, 0, true))
fmt.Println(MorningTwilight(now, 115, 40, -6))
fmt.Println(EveningTwilight(now, 115, 40, -6))
}