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

117 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sundial
import (
"math"
"time"
"b612.me/astro/sun"
)
// TrueSolarTime 真太阳时 / apparent solar time.
//
// 返回 date 在经度 lon 处对应的真太阳时,口径沿用 `sun.ApparentSolarTime`。
// Returns the apparent solar time for date at longitude lon.
func TrueSolarTime(date time.Time, lon float64) time.Time {
return sun.ApparentSolarTime(date, lon)
}
// MeanSolarTime 地方平太阳时 / local mean solar time.
//
// 返回 date 在经度 lon 处对应的地方平太阳时,结果时区为按经度换算的地方平太阳时区。
// 该实现直接按“真太阳时 - 均时差”构造,以与本仓库的真太阳时和均时差口径保持一致。
// Returns the local mean solar time for date at longitude lon. The result uses
// a synthetic local-mean-solar time zone derived from longitude and is built as
// apparent solar time minus equation of time to keep the package's conventions aligned.
func MeanSolarTime(date time.Time, lon float64) time.Time {
return TrueSolarTime(date, lon).Add(time.Duration(-sun.EquationTime(date) * float64(time.Hour)))
}
// HourAngle 太阳时角 / solar hour angle.
//
// 返回经度 lon 处的有符号太阳时角,单位度。上午为负,下午为正,中午为 0°。
// Returns the signed apparent-solar hour angle at longitude lon, in degrees.
func HourAngle(date time.Time, lon float64) float64 {
return normalizeSigned180(sun.HourAngle(date, lon, 0))
}
// MeanSolarHourAngle 平太阳时对应的太阳时角 / hour angle for local mean solar time.
//
// date 负责提供地方平太阳时日期与时区,原有钟面时间会被 meanSolarHours 替换;
// meanSolarHours 为地方平太阳时钟面读数,单位小时,例如 9.5 表示地方平太阳时 09:30。
// 返回对应的视太阳时角,单位度,上午为负,下午为正。
func MeanSolarHourAngle(date time.Time, meanSolarHours float64) float64 {
if !isFinite(meanSolarHours) {
return math.NaN()
}
sampleTime := dateWithClockHours(date, meanSolarHours)
return normalizeSigned180(HourAngle(sampleTime, longitudeFromTimeZone(sampleTime)))
}
// ZoneTimeHourAngle 区时对应的太阳时角 / hour angle for zone time.
//
// zoneTimeHours 为 date 所在时区下的钟面读数单位小时lon 为当地经度,东正西负。
// 返回该区时在给定经度上对应的视太阳时角,单位度,上午为负,下午为正。
// date 提供民用日期和时区;其原有钟面时间会被 zoneTimeHours 替换。
func ZoneTimeHourAngle(date time.Time, lon, zoneTimeHours float64) float64 {
if !isFinite(zoneTimeHours) || !isFinite(lon) {
return math.NaN()
}
return normalizeSigned180(HourAngle(dateWithClockHours(date, zoneTimeHours), lon))
}
// HorizontalHourLineAngle 水平日晷时线角 / horizontal sundial hour-line angle.
//
// lat 为纬度北正南负hourAngle 为有符号太阳时角,单位度。返回值是相对午线的时线角,
// 上午在东侧为负,下午在西侧为正。
// lat is the observer latitude in degrees and hourAngle is the signed solar
// hour angle in degrees. The result is the hour-line angle from the noon line:
// east/morning is negative and west/afternoon is positive.
func HorizontalHourLineAngle(lat, hourAngle float64) float64 {
if !isFinite(hourAngle) || !isFinite(lat) {
return math.NaN()
}
hourAngle = normalizeSigned180(hourAngle)
latRad := lat * math.Pi / 180
hourAngleRad := hourAngle * math.Pi / 180
return math.Atan2(math.Sin(latRad)*math.Sin(hourAngleRad), math.Cos(hourAngleRad)) * 180 / math.Pi
}
// HorizontalHourLineAngleAt 水平日晷时线角 / horizontal sundial hour-line angle.
//
// 先按给定时刻和经度求瞬时视太阳时角,再结合纬度返回水平日晷的时线角。
func HorizontalHourLineAngleAt(date time.Time, lon, lat float64) float64 {
return HorizontalHourLineAngle(lat, HourAngle(date, lon))
}
func dateWithClockHours(date time.Time, hours float64) time.Time {
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
return startOfDay.Add(time.Duration(hours * float64(time.Hour)))
}
func longitudeFromTimeZone(date time.Time) float64 {
_, offsetSeconds := date.Zone()
return float64(offsetSeconds) / 240.0
}
func clockHours(date time.Time) float64 {
return float64(date.Hour()) +
float64(date.Minute())/60 +
float64(date.Second())/3600 +
float64(date.Nanosecond())/3.6e12
}
func normalizeSigned180(value float64) float64 {
value = math.Mod(value, 360)
if value < -180 {
value += 360
}
if value >= 180 {
value -= 360
}
return value
}
func isFinite(value float64) bool {
return !math.IsNaN(value) && !math.IsInf(value, 0)
}