168 lines
6.6 KiB
Go
168 lines
6.6 KiB
Go
|
|
// Package refraction Saemundsson 大气折射及其数值逆解,供 basic 与 formula 共用。
|
|||
|
|
package refraction
|
|||
|
|
|
|||
|
|
import "math"
|
|||
|
|
|
|||
|
|
// StandardPressureHPa 与 StandardTemperatureC 是折射公式的标准气压与气温 / standard pressure and temperature of the refraction formula.
|
|||
|
|
const (
|
|||
|
|
StandardPressureHPa = 1010.0
|
|||
|
|
StandardTemperatureC = 10.0
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
refractionStandardTemperatureK = 283.15
|
|||
|
|
refractionAbsoluteZeroC = -273.15
|
|||
|
|
refractionLowerLimitAltitudeDeg = -5.0
|
|||
|
|
refractionUpperLimitAltitudeDeg = 90.0
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// RefractionFromApparentAltitude 由视高度角求折射量(度,视高度减去它得真高度),无逆解时为 NaN / refraction from apparent altitude.
|
|||
|
|
func RefractionFromApparentAltitude(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
if !validRefractionInputs(apparentAltitude, pressureHPa, temperatureC) {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
if apparentAltitude <= refractionLowerLimitAltitudeDeg || apparentAltitude >= refractionUpperLimitAltitudeDeg {
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
trueAltitude := trueAltitudeFromApparent(apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
return apparentAltitude - trueAltitude
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TrueAltitude 视高度角转真高度角(度),无逆解时为 NaN / true altitude from apparent altitude.
|
|||
|
|
func TrueAltitude(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
if !validRefractionInputs(apparentAltitude, pressureHPa, temperatureC) {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
if apparentAltitude <= refractionLowerLimitAltitudeDeg || apparentAltitude >= refractionUpperLimitAltitudeDeg {
|
|||
|
|
return apparentAltitude
|
|||
|
|
}
|
|||
|
|
return trueAltitudeFromApparent(apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ApparentAltitude 真高度角转视高度角(度) / apparent altitude from true altitude.
|
|||
|
|
func ApparentAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
if !validRefractionInputs(trueAltitude, pressureHPa, temperatureC) {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
return trueAltitude + refractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RefractionFromTrueAltitude 由真高度角求折射量(度,加到真高度上得视高度) / refraction from true altitude.
|
|||
|
|
func RefractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
if !validRefractionInputs(trueAltitude, pressureHPa, temperatureC) {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
return refractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Saemundsson 公式以真高度角为输入;逆函数对同一模型数值求解,保证真/视高度互逆。
|
|||
|
|
func refractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
if trueAltitude <= refractionLowerLimitAltitudeDeg || trueAltitude >= refractionUpperLimitAltitudeDeg {
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
angle := (trueAltitude + 10.3/(trueAltitude+5.11)) * math.Pi / 180
|
|||
|
|
return refractionScale(pressureHPa, temperatureC) * (1.02 / math.Tan(angle)) / 60
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func trueAltitudeFromApparent(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
lower := math.Nextafter(refractionLowerLimitAltitudeDeg, math.Inf(1))
|
|||
|
|
upper := apparentAltitude
|
|||
|
|
// 近 90° 时折射略为负,真高度高于视高度,根区间上界须放宽到 90°。
|
|||
|
|
if refractionFromTrueAltitude(apparentAltitude, pressureHPa, temperatureC) < 0 {
|
|||
|
|
upper = math.Nextafter(refractionUpperLimitAltitudeDeg, math.Inf(-1))
|
|||
|
|
}
|
|||
|
|
if estimate, ok := trueAltitudeFromApparentNewton(apparentAltitude, pressureHPa, temperatureC, lower, upper); ok {
|
|||
|
|
return estimate
|
|||
|
|
}
|
|||
|
|
return trueAltitudeFromApparentBisection(apparentAltitude, pressureHPa, temperatureC, lower, upper)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func trueAltitudeFromApparentNewton(apparentAltitude, pressureHPa, temperatureC, lower, upper float64) (float64, bool) {
|
|||
|
|
estimate := apparentAltitude - refractionFromTrueAltitude(apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
scale := refractionScale(pressureHPa, temperatureC) * 1.02 / 60
|
|||
|
|
for i := 0; i < 12; i++ {
|
|||
|
|
if estimate < lower || estimate > upper || !finiteRefractionValue(estimate) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
value := refractionInverseResidual(estimate, apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
if math.Abs(value) < 1e-12 {
|
|||
|
|
return estimate, true
|
|||
|
|
}
|
|||
|
|
shifted := estimate + 5.11
|
|||
|
|
sinAngle := math.Sin((estimate + 10.3/shifted) * math.Pi / 180)
|
|||
|
|
// d/dh cot(A) = −(dA/dh)/sin²A,A = h + 10.3/(h+5.11)(度)。
|
|||
|
|
derivative := 1 - scale*(1-10.3/(shifted*shifted))*(math.Pi/180)/(sinAngle*sinAngle)
|
|||
|
|
if math.Abs(derivative) < 1e-12 || !finiteRefractionValue(derivative) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
next := estimate - value/derivative
|
|||
|
|
if next < lower || next > upper || !finiteRefractionValue(next) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
estimate = next
|
|||
|
|
}
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func trueAltitudeFromApparentBisection(apparentAltitude, pressureHPa, temperatureC, lower, upper float64) float64 {
|
|||
|
|
lowerValue := refractionInverseResidual(lower, apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
upperValue := refractionInverseResidual(upper, apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
if !finiteRefractionValue(lowerValue) || !finiteRefractionValue(upperValue) || lowerValue > 0 || upperValue < 0 {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
if math.Abs(lowerValue) < 1e-12 {
|
|||
|
|
return lower
|
|||
|
|
}
|
|||
|
|
if math.Abs(upperValue) < 1e-12 {
|
|||
|
|
return upper
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
best, bestResidual := lower, math.Abs(lowerValue)
|
|||
|
|
if math.Abs(upperValue) < bestResidual {
|
|||
|
|
best, bestResidual = upper, math.Abs(upperValue)
|
|||
|
|
}
|
|||
|
|
for i := 0; i < 96; i++ {
|
|||
|
|
midpoint := lower + (upper-lower)/2
|
|||
|
|
if midpoint == lower || midpoint == upper {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
value := refractionInverseResidual(midpoint, apparentAltitude, pressureHPa, temperatureC)
|
|||
|
|
if !finiteRefractionValue(value) {
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
if residual := math.Abs(value); residual < bestResidual {
|
|||
|
|
best, bestResidual = midpoint, residual
|
|||
|
|
}
|
|||
|
|
if math.Abs(value) < 1e-12 {
|
|||
|
|
return midpoint
|
|||
|
|
}
|
|||
|
|
if value > 0 {
|
|||
|
|
upper = midpoint
|
|||
|
|
} else {
|
|||
|
|
lower = midpoint
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if bestResidual <= 1e-9 {
|
|||
|
|
return best
|
|||
|
|
}
|
|||
|
|
return math.NaN()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func refractionInverseResidual(trueAltitude, apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
|||
|
|
return trueAltitude + refractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC) - apparentAltitude
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func finiteRefractionValue(value float64) bool {
|
|||
|
|
return !math.IsNaN(value) && !math.IsInf(value, 0)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func validRefractionInputs(altitude, pressureHPa, temperatureC float64) bool {
|
|||
|
|
return !(math.IsNaN(altitude) || math.IsInf(altitude, 0) ||
|
|||
|
|
math.IsNaN(pressureHPa) || math.IsInf(pressureHPa, 0) || pressureHPa <= 0 ||
|
|||
|
|
math.IsNaN(temperatureC) || math.IsInf(temperatureC, 0) || temperatureC <= refractionAbsoluteZeroC)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func refractionScale(pressureHPa, temperatureC float64) float64 {
|
|||
|
|
return pressureHPa / StandardPressureHPa * refractionStandardTemperatureK / (temperatureC - refractionAbsoluteZeroC)
|
|||
|
|
}
|