117 lines
4.6 KiB
Go
117 lines
4.6 KiB
Go
|
|
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)
|
|||
|
|
}
|