156 lines
6.9 KiB
Go
156 lines
6.9 KiB
Go
|
|
package basic
|
||
|
|
|
||
|
|
import "math"
|
||
|
|
|
||
|
|
// solarEclipseLimbMaxShiftDays 是全球偏食始末月缘修正的上限(2 分钟),超出视为不可信并保留圆月面结果。
|
||
|
|
const solarEclipseLimbMaxShiftDays = 120.0 / 86400.0
|
||
|
|
|
||
|
|
// solarEclipseLimbDerivativeStepDays 是求外切间隙时间导数的差分半步长(10 秒)。
|
||
|
|
const solarEclipseLimbDerivativeStepDays = 10.0 / 86400.0
|
||
|
|
|
||
|
|
// SolarEclipseLimbContact 月缘修正后的全球偏食始末接触点,与修正后的时刻同源 / limb-corrected global contact location.
|
||
|
|
type SolarEclipseLimbContact struct {
|
||
|
|
// Defined 为真时经纬度有效;月缘未生效时保持 false 并且经纬度为零值 / the location is valid.
|
||
|
|
Defined bool
|
||
|
|
// Longitude 经度,东正西负,单位度 / longitude in degrees, east positive.
|
||
|
|
Longitude float64
|
||
|
|
// Latitude 纬度,北正南负,单位度 / latitude in degrees, north positive.
|
||
|
|
Latitude float64
|
||
|
|
}
|
||
|
|
|
||
|
|
// withLunarLimb 在圆月面全球结果上按月缘修正偏食始末(P1/P4)与食甚点中心食时长;中心线始末、食甚、食分与食型不变,
|
||
|
|
// 本函数不改带宽,月缘带宽增量由路径层在成对横截面上叠加。
|
||
|
|
func (solver solarEclipseSolver) withLunarLimb(result SolarEclipseResult, limb LunarLimb) SolarEclipseResult {
|
||
|
|
if limb == nil || result.Type == SolarEclipseNone {
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
limbSolver := solver.withLimbParams(limb)
|
||
|
|
|
||
|
|
original := result
|
||
|
|
if result.HasPartial {
|
||
|
|
result.PartialBeginOnEarth, result.PartialBeginContact = solver.limbPartialContactPoint(limbSolver, result.PartialBeginOnEarth)
|
||
|
|
result.PartialEndOnEarth, result.PartialEndContact = solver.limbPartialContactPoint(limbSolver, result.PartialEndOnEarth)
|
||
|
|
}
|
||
|
|
if result.HasCentral && result.GreatestEclipse > 0 {
|
||
|
|
// 剖面在任一求解状态上无值时,半径已退回圆月面,保留圆月面时长而不是采纳离散差。
|
||
|
|
if duration, limbOK := limbSolver.centralPhaseDurationDaysWithLimbAt(
|
||
|
|
result.GreatestEclipse, result.GreatestLongitude, result.GreatestLatitude,
|
||
|
|
); limbOK && duration > 0 {
|
||
|
|
result.CentralDurationDays = duration
|
||
|
|
}
|
||
|
|
}
|
||
|
|
result.LunarLimbApplied = solarEclipseLimbChanged(result, original)
|
||
|
|
// 时刻未真正改变时位置也不该被声明,避免出图标记用上噪声级的平移。
|
||
|
|
if !result.LunarLimbApplied {
|
||
|
|
result.PartialBeginContact, result.PartialEndContact = SolarEclipseLimbContact{}, SolarEclipseLimbContact{}
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// limbPartialContactPoint 在修正后的时刻重解半影与地球的外切点,使接触点位置与新时刻同源。
|
||
|
|
func (solver solarEclipseSolver) limbPartialContactPoint(
|
||
|
|
limbSolver solarEclipseSolver,
|
||
|
|
contactJDE float64,
|
||
|
|
) (float64, SolarEclipseLimbContact) {
|
||
|
|
jde, ok := solver.limbPartialContact(limbSolver, contactJDE)
|
||
|
|
if !ok {
|
||
|
|
return jde, SolarEclipseLimbContact{}
|
||
|
|
}
|
||
|
|
point, pointOK := solver.shadowContactPointAt(jde, solarEclipsePenumbralShadow, false)
|
||
|
|
if !pointOK {
|
||
|
|
return jde, SolarEclipseLimbContact{}
|
||
|
|
}
|
||
|
|
return jde, SolarEclipseLimbContact{Defined: true, Longitude: point.Longitude, Latitude: point.Latitude}
|
||
|
|
}
|
||
|
|
|
||
|
|
func solarEclipseLimbChanged(result, original SolarEclipseResult) bool {
|
||
|
|
for _, pair := range [][2]float64{
|
||
|
|
{result.PartialBeginOnEarth, original.PartialBeginOnEarth},
|
||
|
|
{result.PartialEndOnEarth, original.PartialEndOnEarth},
|
||
|
|
{result.CentralDurationDays, original.CentralDurationDays},
|
||
|
|
} {
|
||
|
|
if limbTimeChanged(pair[0], pair[1]) {
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
// solarEclipseLimbSiteSpanRad 与 solarEclipseLimbSiteStepRad 是切点两侧沿地球边缘扫描的半宽与步长:
|
||
|
|
// 月缘起伏约 ±5 km,而偏离切点 δ 时影锥外距约 R⊕·δ²/2,δ 超过约 3° 后不可能更早接触。
|
||
|
|
const (
|
||
|
|
solarEclipseLimbSiteSpanRad = 3.0 * math.Pi / 180
|
||
|
|
solarEclipseLimbSiteStepRad = 0.1 * math.Pi / 180
|
||
|
|
)
|
||
|
|
|
||
|
|
// limbPartialContact 修正一次全球偏食始末,P1 取最早、P4 取最晚;第二个返回值报告是否取到月缘解。
|
||
|
|
func (solver solarEclipseSolver) limbPartialContact(limbSolver solarEclipseSolver, contactJDE float64) (float64, bool) {
|
||
|
|
if !finite(contactJDE) || contactJDE <= 0 {
|
||
|
|
return contactJDE, false
|
||
|
|
}
|
||
|
|
moon := solver.besselMoonAt(contactJDE)
|
||
|
|
norm := math.Hypot(moon[0], moon[1])
|
||
|
|
if !finite(norm) || norm == 0 {
|
||
|
|
return contactJDE, false
|
||
|
|
}
|
||
|
|
axis := solver.besselAxisAt(contactJDE)
|
||
|
|
center := math.Atan2(moon[1], moon[0])
|
||
|
|
siteAt := func(angle float64) (float64, float64, bool) {
|
||
|
|
x, y := math.Cos(angle), math.Sin(angle)
|
||
|
|
// 与 quickContactAt 同口径的椭球有效半径,再稍向内收保证视线与椭球相交。
|
||
|
|
effective := 1 - (1/solarEclipseEarthPolarRatioSquared-1)*y*y/2
|
||
|
|
lonDeg, latDeg, ok := solarEclipseBesselXYToGeodetic(x*effective*0.999, y*effective*0.999, axis, true)
|
||
|
|
return lonDeg * rad, latDeg * rad, ok
|
||
|
|
}
|
||
|
|
gapAt := func(s solarEclipseSolver, lonRad, latRad, jde float64) float64 {
|
||
|
|
return s.localStateContextAt(jde).stateAt(lonRad, latRad, 0).movingDiskContactState().externalContactGap()
|
||
|
|
}
|
||
|
|
// 剖面在该点无值时半径退回圆月面,这样的点不能参与修正,否则圆月面离散误差会冒充月缘修正。
|
||
|
|
limbGapAt := func(lonRad, latRad, jde float64) (float64, bool) {
|
||
|
|
state := limbSolver.localStateContextAt(jde).stateAt(lonRad, latRad, 0)
|
||
|
|
return state.movingDiskContactState().externalContactGap(), state.outerLimbApplied
|
||
|
|
}
|
||
|
|
lon0, lat0, ok := siteAt(center)
|
||
|
|
if !ok {
|
||
|
|
return contactJDE, false
|
||
|
|
}
|
||
|
|
reference := gapAt(solver, lon0, lat0, contactJDE)
|
||
|
|
after, okAfter := limbGapAt(lon0, lat0, contactJDE+solarEclipseLimbDerivativeStepDays)
|
||
|
|
before, okBefore := limbGapAt(lon0, lat0, contactJDE-solarEclipseLimbDerivativeStepDays)
|
||
|
|
derivative := (after - before) / (2 * solarEclipseLimbDerivativeStepDays)
|
||
|
|
if !okAfter || !okBefore || !finite(reference) || !finite(derivative) || derivative == 0 {
|
||
|
|
return contactJDE, false
|
||
|
|
}
|
||
|
|
// derivative < 0 表示间隙随时间缩小(P1),此时取最早接触;否则取最晚(P4)。
|
||
|
|
earliest := derivative < 0
|
||
|
|
best, found := 0.0, false
|
||
|
|
steps := int(math.Round(solarEclipseLimbSiteSpanRad / solarEclipseLimbSiteStepRad))
|
||
|
|
for i := -steps; i <= steps; i++ {
|
||
|
|
lonRad, latRad, ok := siteAt(center + float64(i)*solarEclipseLimbSiteStepRad)
|
||
|
|
if !ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
gap, limbOK := limbGapAt(lonRad, latRad, contactJDE)
|
||
|
|
if !limbOK || !finite(gap) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
shift := -(gap - reference) / derivative
|
||
|
|
if !found || (earliest && shift < best) || (!earliest && shift > best) {
|
||
|
|
best, found = shift, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if !found || !finite(best) || math.Abs(best) > solarEclipseLimbMaxShiftDays {
|
||
|
|
return contactJDE, false
|
||
|
|
}
|
||
|
|
return contactJDE + best, true
|
||
|
|
}
|
||
|
|
|
||
|
|
// withLimbParams 返回带月缘、独立状态缓存的求解器副本;原求解器的圆月面缓存不受影响。
|
||
|
|
func (solver solarEclipseSolver) withLimbParams(limb LunarLimb) solarEclipseSolver {
|
||
|
|
limbSolver := solver
|
||
|
|
limbSolver.params.lunarLimb = limb
|
||
|
|
limbSolver.localStateContextCache = make(map[uint64]localSolarEclipseStateContext)
|
||
|
|
limbSolver.limbAxes = nil
|
||
|
|
return limbSolver
|
||
|
|
}
|