Files
astro/basic/sun_observation.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

555 lines
20 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 basic
import (
"math"
. "b612.me/astro/tools"
)
// CulminationTime 太阳中天时刻(按均时差计算)/ solar culmination time from the equation of time.
//
// 日期锚点是 floor(jd)(JD 整数 = 12:00 UT 的正午锚点),不是午夜;调用方要传“本地 0 时对应 JD + 0.5”
// 才能落在同一本地日(sun/sun.go 的 CulminationTime 就是这么补的)。地方时相对世界时的偏移按角度归化到
// ±180°:超过 ±12 小时(如 UTC+14 配西经)时不归化会把中天推到相邻的一天。
func CulminationTime(jd, lon, tz float64) float64 { //实际中天时间
jd = math.Floor(jd)
tmp := longitudeOffsetDegrees(tz*15-lon) * 4 / 60
return jd + tmp/24.0 - SunTime(jd)/24.0
}
// CulminationTimeN 截断项太阳中天时刻 / truncated solar culmination time.
func CulminationTimeN(jd, lon, tz float64, n int) float64 { //实际中天时间
jd = math.Floor(jd)
tmp := longitudeOffsetDegrees(tz*15-lon) * 4 / 60
return jd + tmp/24.0 - SunTimeN(jd, n)/24.0
}
func longitudeOffsetDegrees(offset float64) float64 {
if offset > 180 {
offset -= 360
}
if offset < -180 {
offset += 360
}
return offset
}
/*
* 昏朦影传入 当天0时时刻
*/
func EveningTwilight(jd, lon, lat, tz, targetAltitude float64) (float64, error) {
jd = math.Floor(jd) + 1.5
localTimeZone := math.Round(lon / 15)
culminationTime := CulminationTime(jd, lon, localTimeZone)
if SunHeight(culminationTime, lon, lat, localTimeZone) < targetAltitude {
return 0, ErrNeverRise
}
if SunHeight(culminationTime+0.5, lon, lat, localTimeZone) > targetAltitude {
return 0, ErrNeverSet
}
tmp := (Sin(targetAltitude) - Sin(HSunApparentDec(culminationTime))*Sin(lat)) / (Cos(HSunApparentDec(culminationTime)) * Cos(lat))
var sundown float64
if math.Abs(tmp) <= 1 && lat < 85 {
hourOffset := ArcCos(tmp) / 15
sundown = culminationTime + hourOffset/24.0 + 35.0/24.0/60.0
} else {
sundown = culminationTime
i := 0
for LowSunHeight(sundown, lon, lat, localTimeZone) > targetAltitude {
i++
sundown += 15.0 / 60.0 / 24.0
if i > 48 {
break
}
}
}
estimateJD := sundown - 5.00/24.00/60.00
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
stDegree := SunHeight(prevJD, lon, lat, localTimeZone) - targetAltitude
stDegreep := (SunHeight(prevJD+0.000005, lon, lat, localTimeZone) - SunHeight(prevJD-0.000005, lon, lat, localTimeZone)) / 0.00001
return stDegree / stDegreep
})
if !ok {
return 0, ErrInvalidObservationInput
}
return estimateJD - localTimeZone/24 + tz/24, nil
}
func EveningTwilightN(jd, lon, lat, tz, targetAltitude float64, n int) (float64, error) {
jd = math.Floor(jd) + 1.5
localTimeZone := math.Round(lon / 15)
culminationTime := CulminationTimeN(jd, lon, localTimeZone, n)
if SunHeightN(culminationTime, lon, lat, localTimeZone, n) < targetAltitude {
return 0, ErrNeverRise
}
if SunHeightN(culminationTime+0.5, lon, lat, localTimeZone, n) > targetAltitude {
return 0, ErrNeverSet
}
tmp := (Sin(targetAltitude) - Sin(HSunApparentDecN(culminationTime, n))*Sin(lat)) / (Cos(HSunApparentDecN(culminationTime, n)) * Cos(lat))
var sundown float64
if math.Abs(tmp) <= 1 && lat < 85 {
hourOffset := ArcCos(tmp) / 15
sundown = culminationTime + hourOffset/24.0 + 35.0/24.0/60.0
} else {
sundown = culminationTime
i := 0
for lowSunHeightForN(sundown, lon, lat, localTimeZone, n) > targetAltitude {
i++
sundown += 15.0 / 60.0 / 24.0
if i > 48 {
break
}
}
}
estimateJD := sundown - 5.00/24.00/60.00
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
stDegree := SunHeightN(prevJD, lon, lat, localTimeZone, n) - targetAltitude
stDegreep := (SunHeightN(prevJD+0.000005, lon, lat, localTimeZone, n) - SunHeightN(prevJD-0.000005, lon, lat, localTimeZone, n)) / 0.00001
return stDegree / stDegreep
})
if !ok {
return 0, ErrInvalidObservationInput
}
return estimateJD - localTimeZone/24 + tz/24, nil
}
func MorningTwilight(jd, lon, lat, tz, targetAltitude float64) (float64, error) {
// 调整到中午12点
jd = math.Floor(jd) + 1.5
// 计算时区
localTimeZone := math.Round(lon / 15)
// 计算太阳上中天时间
culminationTime := CulminationTime(jd, lon, localTimeZone)
// 检查极夜和极昼条件
if SunHeight(culminationTime, lon, lat, localTimeZone) < targetAltitude {
return 0, ErrNeverRise
}
if SunHeight(culminationTime-0.5, lon, lat, localTimeZone) > targetAltitude {
return 0, ErrNeverSet
}
// 计算日出时间
sunDec := HSunApparentDec(culminationTime)
tmp := (Sin(targetAltitude) - Sin(sunDec)*Sin(lat)) / (Cos(sunDec) * Cos(lat))
var sunrise float64
if math.Abs(tmp) <= 1 && lat < 85 {
hourAngle := ArcCos(tmp) / 15
sunrise = culminationTime - hourAngle/24 - 25.0/(24.0*60.0)
} else {
sunrise = culminationTime
for i := 0; i < 48 && LowSunHeight(sunrise, lon, lat, localTimeZone) > targetAltitude; i++ {
sunrise -= 15.0 / (60.0 * 24.0) // 每次减少15分钟
}
}
estimateJD := sunrise - 5.0/(24.0*60.0)
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
heightDiff := SunHeight(prevJD, lon, lat, localTimeZone) - targetAltitude
heightDerivative := (SunHeight(prevJD+0.000005, lon, lat, localTimeZone) - SunHeight(prevJD-0.000005, lon, lat, localTimeZone)) / 0.00001
return heightDiff / heightDerivative
})
if !ok {
return 0, ErrInvalidObservationInput
}
return estimateJD - localTimeZone/24 + tz/24, nil
}
func MorningTwilightN(jd, lon, lat, tz, targetAltitude float64, n int) (float64, error) {
jd = math.Floor(jd) + 1.5
localTimeZone := math.Round(lon / 15)
culminationTime := CulminationTimeN(jd, lon, localTimeZone, n)
if SunHeightN(culminationTime, lon, lat, localTimeZone, n) < targetAltitude {
return 0, ErrNeverRise
}
if SunHeightN(culminationTime-0.5, lon, lat, localTimeZone, n) > targetAltitude {
return 0, ErrNeverSet
}
sunDec := HSunApparentDecN(culminationTime, n)
tmp := (Sin(targetAltitude) - Sin(sunDec)*Sin(lat)) / (Cos(sunDec) * Cos(lat))
var sunrise float64
if math.Abs(tmp) <= 1 && lat < 85 {
hourAngle := ArcCos(tmp) / 15
sunrise = culminationTime - hourAngle/24 - 25.0/(24.0*60.0)
} else {
sunrise = culminationTime
for i := 0; i < 48 && lowSunHeightForN(sunrise, lon, lat, localTimeZone, n) > targetAltitude; i++ {
sunrise -= 15.0 / (60.0 * 24.0)
}
}
estimateJD := sunrise - 5.0/(24.0*60.0)
var ok bool
estimateJD, ok = eventNewtonRefine(estimateJD, 0.00001, func(prevJD float64) float64 {
heightDiff := SunHeightN(prevJD, lon, lat, localTimeZone, n) - targetAltitude
heightDerivative := (SunHeightN(prevJD+0.000005, lon, lat, localTimeZone, n) - SunHeightN(prevJD-0.000005, lon, lat, localTimeZone, n)) / 0.00001
return heightDiff / heightDerivative
})
if !ok {
return 0, ErrInvalidObservationInput
}
return estimateJD - localTimeZone/24 + tz/24, nil
}
/*
* 太阳时角
*/
func SunTimeAngle(jd, lon, lat, tz float64) float64 {
startime := Limit360(ApparentSiderealTime(jd-tz/24)*15 + lon)
timeangle := startime - HSunApparentRa(TD2UT(jd-tz/24, true))
if timeangle < 0 {
timeangle += 360
}
return timeangle
}
func SunTimeAngleN(jd, lon, lat, tz float64, n int) float64 {
startime := Limit360(ApparentSiderealTime(jd-tz/24)*15 + lon)
timeangle := startime - HSunApparentRaN(TD2UT(jd-tz/24, true), n)
if timeangle < 0 {
timeangle += 360
}
return timeangle
}
type sunObservationState struct {
altitude float64
distanceAU float64
}
func sunObservationStateN(jd, lon, lat, tz float64, n int) sunObservationState {
calculationJD := jd - tz/24.0
tt := TD2UT(calculationJD, true)
siderealTime := Limit360(ApparentSiderealTime(calculationJD)*15 + lon)
ra, dec, distanceAU := hSunApparentRaDecDistanceN(tt, n)
hourAngle := Limit360(siderealTime - ra)
altitudeSine := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
return sunObservationState{
altitude: ArcSin(altitudeSine),
distanceAU: distanceAU,
}
}
func sunRiseSetResidual(jd, longitude, latitude, timeZone, zenithShift, height float64, n int) float64 {
state := sunObservationStateN(jd, longitude, latitude, timeZone, n)
// 相对观测者下沉地平线的视上缘高度角 / Apparent upper-limb altitude relative to the observer's depressed horizon.
residual := state.altitude + HeightDegreeByLat(height, latitude)
if zenithShift != 0 {
residual += RefractionFromTrueAltitude(state.altitude, refractionStandardPressureHPa, refractionStandardTemperatureC)
residual += angularSemidiameterFromAU(sunEquatorialRadiusKM, state.distanceAU) / 3600
}
return residual
}
func sunRiseSetOnCivilDay(candidate, slope, civilDayStart, longitude, latitude, requestedTimeZone,
localTimeZone, zenithShift, height float64, isSunrise bool, n int, fallbackErr error) (float64, error) {
if eventRiseSetCandidateValid(candidate, civilDayStart, slope, isSunrise) {
return candidate, nil
}
return eventDirectionalRiseSetSearch(civilDayStart, isSunrise, fallbackErr, func(outputJD float64) float64 {
localJD := outputJD + localTimeZone/24 - requestedTimeZone/24
return sunRiseSetResidual(localJD, longitude, latitude, localTimeZone, zenithShift, height, n)
})
}
// GetSunRiseTime 精确计算日出时间,传入当日0时JDE
func GetSunRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
return calculateSunRiseSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height, true)
}
func GetSunRiseTimeN(julianDay, longitude, latitude, timeZone, zenithShift, height float64, n int) (float64, error) {
return calculateSunRiseSetTimeN(julianDay, longitude, latitude, timeZone, zenithShift, height, true, n)
}
// GetSunSetTime 精确计算日落时间,传入当日0时JDE
func GetSunSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
return calculateSunRiseSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height, false)
}
func GetSunSetTimeN(julianDay, longitude, latitude, timeZone, zenithShift, height float64, n int) (float64, error) {
return calculateSunRiseSetTimeN(julianDay, longitude, latitude, timeZone, zenithShift, height, false, n)
}
// calculateSunRiseSetTime 统一的日出日落计算函数
func calculateSunRiseSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64, isSunrise bool) (float64, error) {
if !isFiniteFloat(julianDay) || !isFiniteFloat(longitude) || !isFiniteFloat(latitude) || !isFiniteFloat(timeZone) || !isFiniteFloat(zenithShift) || !isFiniteFloat(height) {
return 0, ErrInvalidObservationInput
}
civilDayStart := math.Floor(julianDay) + 0.5
julianDay = math.Floor(julianDay) + 1.5
naturalTimeZone := math.Round(longitude / 15)
sunAngle := StandardAltitudeSun(zenithShift, height, latitude)
// 获取太阳上中天时间
solarNoonTime := CulminationTime(julianDay, longitude, naturalTimeZone)
// 检查极夜极昼条件
if err := checkPolarConditions(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height, isSunrise); err != nil {
return sunRiseSetOnCivilDay(math.NaN(), math.NaN(), civilDayStart, longitude, latitude, timeZone,
naturalTimeZone, zenithShift, height, isSunrise, -1, err)
}
// 计算初始估算时间
initialTime := calculateInitialSunTime(solarNoonTime, longitude, latitude, naturalTimeZone, sunAngle, zenithShift, height, isSunrise)
// 牛顿-拉夫逊迭代求精确解
result, slope := sunRiseSetNewtonRaphsonIteration(initialTime, longitude, latitude, naturalTimeZone, zenithShift, height, timeZone)
return sunRiseSetOnCivilDay(result, slope, civilDayStart, longitude, latitude, timeZone,
naturalTimeZone, zenithShift, height, isSunrise, -1, nil)
}
func calculateSunRiseSetTimeN(julianDay, longitude, latitude, timeZone, zenithShift, height float64, isSunrise bool, n int) (float64, error) {
if !isFiniteFloat(julianDay) || !isFiniteFloat(longitude) || !isFiniteFloat(latitude) || !isFiniteFloat(timeZone) || !isFiniteFloat(zenithShift) || !isFiniteFloat(height) {
return 0, ErrInvalidObservationInput
}
civilDayStart := math.Floor(julianDay) + 0.5
julianDay = math.Floor(julianDay) + 1.5
naturalTimeZone := math.Round(longitude / 15)
sunAngle := StandardAltitudeSun(zenithShift, height, latitude)
solarNoonTime := CulminationTimeN(julianDay, longitude, naturalTimeZone, n)
if err := checkPolarConditionsN(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height, isSunrise, n); err != nil {
return sunRiseSetOnCivilDay(math.NaN(), math.NaN(), civilDayStart, longitude, latitude, timeZone,
naturalTimeZone, zenithShift, height, isSunrise, n, err)
}
initialTime := calculateInitialSunTimeN(solarNoonTime, longitude, latitude, naturalTimeZone, sunAngle, zenithShift, height, isSunrise, n)
result, slope := sunRiseSetNewtonRaphsonIterationN(initialTime, longitude, latitude, naturalTimeZone, zenithShift, height, timeZone, n)
return sunRiseSetOnCivilDay(result, slope, civilDayStart, longitude, latitude, timeZone,
naturalTimeZone, zenithShift, height, isSunrise, n, nil)
}
// checkPolarConditions 检查极夜极昼条件
func checkPolarConditions(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height float64, isSunrise bool) error {
if sunRiseSetResidual(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height, -1) < 0 {
return ErrNeverRise
}
checkTime := solarNoonTime + 0.5
if isSunrise {
checkTime = solarNoonTime - 0.5
}
if sunRiseSetResidual(checkTime, longitude, latitude, naturalTimeZone, zenithShift, height, -1) > 0 {
return ErrNeverSet
}
return nil
}
func checkPolarConditionsN(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height float64, isSunrise bool, n int) error {
if sunRiseSetResidual(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height, n) < 0 {
return ErrNeverRise
}
checkTime := solarNoonTime + 0.5
if isSunrise {
checkTime = solarNoonTime - 0.5
}
if sunRiseSetResidual(checkTime, longitude, latitude, naturalTimeZone, zenithShift, height, n) > 0 {
return ErrNeverSet
}
return nil
}
// calculateInitialSunTime 计算日出日落的初始估算时间
func calculateInitialSunTime(solarNoonTime, longitude, latitude, naturalTimeZone, sunAngle, zenithShift, height float64, isSunrise bool) float64 {
// 使用球面三角法计算: (sin(ho)-sin(φ)*sin(δ))/(cos(φ)*cos(δ))
apparentDeclination := HSunApparentDec(solarNoonTime)
cosHourAngle := (Sin(sunAngle) - Sin(apparentDeclination)*Sin(latitude)) / (Cos(apparentDeclination) * Cos(latitude))
if math.Abs(cosHourAngle) <= 1 && latitude < 85 {
// 使用解析解
hourAngle := ArcCos(cosHourAngle) / 15
timeOffset := 25.0 / 24.0 / 60.0 // 日出偏移
if !isSunrise {
timeOffset = 35.0 / 24.0 / 60.0 // 日落偏移
}
if isSunrise {
return solarNoonTime - hourAngle/24 - timeOffset
} else {
return solarNoonTime + hourAngle/24 + timeOffset
}
} else {
// 使用迭代逼近法(极地条件)
return iterativeApproach(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height, isSunrise)
}
}
func calculateInitialSunTimeN(solarNoonTime, longitude, latitude, naturalTimeZone, sunAngle, zenithShift, height float64, isSunrise bool, n int) float64 {
apparentDeclination := HSunApparentDecN(solarNoonTime, n)
cosHourAngle := (Sin(sunAngle) - Sin(apparentDeclination)*Sin(latitude)) / (Cos(apparentDeclination) * Cos(latitude))
if math.Abs(cosHourAngle) <= 1 && latitude < 85 {
hourAngle := ArcCos(cosHourAngle) / 15
timeOffset := 25.0 / 24.0 / 60.0
if !isSunrise {
timeOffset = 35.0 / 24.0 / 60.0
}
if isSunrise {
return solarNoonTime - hourAngle/24 - timeOffset
}
return solarNoonTime + hourAngle/24 + timeOffset
}
return iterativeApproachN(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height, isSunrise, n)
}
// iterativeApproach 迭代逼近法计算(用于极地等特殊条件)
func iterativeApproach(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height float64, isSunrise bool) float64 {
estimatedTime := solarNoonTime
stepSize := 15.0 / 60.0 / 24.0 // 15分钟步长
if isSunrise {
stepSize = -stepSize
}
const maxIterations = 48
for i := 0; i < maxIterations && sunRiseSetResidual(estimatedTime, longitude, latitude, naturalTimeZone, zenithShift, height, -1) > 0; i++ {
estimatedTime += stepSize
}
return estimatedTime
}
func iterativeApproachN(solarNoonTime, longitude, latitude, naturalTimeZone, zenithShift, height float64, isSunrise bool, n int) float64 {
estimatedTime := solarNoonTime
stepSize := 15.0 / 60.0 / 24.0
if isSunrise {
stepSize = -stepSize
}
const maxIterations = 48
for i := 0; i < maxIterations && sunRiseSetResidual(estimatedTime, longitude, latitude, naturalTimeZone, zenithShift, height, n) > 0; i++ {
estimatedTime += stepSize
}
return estimatedTime
}
// sunRiseSetNewtonRaphsonIteration 牛顿-拉夫逊迭代法求精确解
func sunRiseSetNewtonRaphsonIteration(initialTime, longitude, latitude, naturalTimeZone, zenithShift, height, timeZone float64) (float64, float64) {
const (
convergenceThreshold = 0.00001
derivativeStep = 0.000005
)
currentTime := initialTime
slope := math.NaN()
var ok bool
currentTime, ok = eventNewtonRefine(currentTime, convergenceThreshold, func(previousTime float64) float64 {
functionValue := sunRiseSetResidual(previousTime, longitude, latitude, naturalTimeZone, zenithShift, height, -1)
slope = (sunRiseSetResidual(previousTime+derivativeStep, longitude, latitude, naturalTimeZone, zenithShift, height, -1) -
sunRiseSetResidual(previousTime-derivativeStep, longitude, latitude, naturalTimeZone, zenithShift, height, -1)) / (2 * derivativeStep)
return functionValue / slope
})
if !ok {
return math.NaN(), math.NaN()
}
// 转换为指定时区
return currentTime - naturalTimeZone/24 + timeZone/24, slope
}
func sunRiseSetNewtonRaphsonIterationN(initialTime, longitude, latitude, naturalTimeZone, zenithShift, height, timeZone float64, n int) (float64, float64) {
const (
convergenceThreshold = 0.00001
derivativeStep = 0.000005
)
currentTime := initialTime
slope := math.NaN()
var ok bool
currentTime, ok = eventNewtonRefine(currentTime, convergenceThreshold, func(previousTime float64) float64 {
functionValue := sunRiseSetResidual(previousTime, longitude, latitude, naturalTimeZone, zenithShift, height, n)
slope = (sunRiseSetResidual(previousTime+derivativeStep, longitude, latitude, naturalTimeZone, zenithShift, height, n) -
sunRiseSetResidual(previousTime-derivativeStep, longitude, latitude, naturalTimeZone, zenithShift, height, n)) / (2 * derivativeStep)
return functionValue / slope
})
if !ok {
return math.NaN(), math.NaN()
}
return currentTime - naturalTimeZone/24 + timeZone/24, slope
}
/*
* 太阳高度角 世界时
*/
func SunHeight(jd, lon, lat, tz float64) float64 {
return SunHeightN(jd, lon, lat, tz, -1)
}
func SunHeightN(jd, lon, lat, tz float64, n int) float64 {
return sunObservationStateN(jd, lon, lat, tz, n).altitude
}
func LowSunHeight(jd, lon, lat, tz float64) float64 {
//tmp := (tz*15 - lon) * 4 / 60
//truejd := jd - tmp/24
calcjd := jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - SunApparentRa(TD2UT(calcjd, true)))
dec := SunApparentDec(TD2UT(calcjd, true))
tmp2 := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
return ArcSin(tmp2)
}
func lowSunHeightForN(jd, lon, lat, tz float64, n int) float64 {
if n < 0 {
return LowSunHeight(jd, lon, lat, tz)
}
return SunHeightN(jd, lon, lat, tz, n)
}
func SunAzimuth(jd, lon, lat, tz float64) float64 {
//tmp := (tz*15 - lon) * 4 / 60
//truejd := jd - tmp/24
calcjd := jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - HSunApparentRa(TD2UT(calcjd, true)))
tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(HSunApparentDec(TD2UT(calcjd, true)))*Cos(lat))
azimuth := ArcTan(tmp2)
if azimuth < 0 {
if hourAngle/15 < 12 {
return azimuth + 360
}
return azimuth + 180
}
if hourAngle/15 < 12 {
return azimuth + 180
}
return azimuth
}
func SunAzimuthN(jd, lon, lat, tz float64, n int) float64 {
calcjd := jd - tz/24
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
hourAngle := Limit360(st - HSunApparentRaN(TD2UT(calcjd, true), n))
tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(HSunApparentDecN(TD2UT(calcjd, true), n))*Cos(lat))
azimuth := ArcTan(tmp2)
if azimuth < 0 {
if hourAngle/15 < 12 {
return azimuth + 360
}
return azimuth + 180
}
if hourAngle/15 < 12 {
return azimuth + 180
}
return azimuth
}