521 lines
17 KiB
Go
521 lines
17 KiB
Go
|
|
package basic
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"math"
|
|||
|
|
|
|||
|
|
. "b612.me/astro/tools"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
planetTransitMeanSolarMotionDegPerDay = 360.0 / 365.2422
|
|||
|
|
planetTransitTropicalYearDays = 365.2422
|
|||
|
|
planetTransitSeasonProbeStepDays = 20.0
|
|||
|
|
planetTransitSearchLimit = 2400
|
|||
|
|
planetTransitSearchEpsilonDays = 1.0 / 86400.0
|
|||
|
|
planetTransitGreatestWindowDays = 1.2
|
|||
|
|
planetTransitGreatestToleranceDays = 0.25 / 86400.0
|
|||
|
|
planetTransitContactStepDays = 0.02
|
|||
|
|
planetTransitContactSpanDays = 1.0
|
|||
|
|
planetTransitContactToleranceDays = 0.25 / 86400.0
|
|||
|
|
planetTransitCoarseN = 16
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// PlanetTransitResult 表示一次地心行星凌日结果。
|
|||
|
|
//
|
|||
|
|
// Valid 为 false 时表示没有找到有效凌日。所有时刻字段均为 UT 儒略日。
|
|||
|
|
// MinimumSeparationArcsec、SunSemidiameterArcsec、PlanetSemidiameterArcsec 的单位均为角秒。
|
|||
|
|
type PlanetTransitResult struct {
|
|||
|
|
Valid bool
|
|||
|
|
|
|||
|
|
// PlanetIndex 为行星序号,1 表示水星,2 表示金星。
|
|||
|
|
PlanetIndex int
|
|||
|
|
|
|||
|
|
// ExternalIngress / ExternalEgress 为一触 / 四触。
|
|||
|
|
ExternalIngress float64
|
|||
|
|
ExternalEgress float64
|
|||
|
|
// InternalIngress / InternalEgress 为二触 / 三触。掠凌没有内切接触时为 0。
|
|||
|
|
InternalIngress float64
|
|||
|
|
InternalEgress float64
|
|||
|
|
// Greatest 为凌甚,即行星中心最接近太阳中心的时刻。
|
|||
|
|
Greatest float64
|
|||
|
|
|
|||
|
|
MinimumSeparationArcsec float64
|
|||
|
|
SunSemidiameterArcsec float64
|
|||
|
|
PlanetSemidiameterArcsec float64
|
|||
|
|
|
|||
|
|
HasExternal bool
|
|||
|
|
HasInternal bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type planetTransitConfig struct {
|
|||
|
|
planetIndex int
|
|||
|
|
|
|||
|
|
synodicPeriodDays float64
|
|||
|
|
anchorInferiorTT float64
|
|||
|
|
|
|||
|
|
seasonWindowDays float64
|
|||
|
|
latitudePrefilter float64
|
|||
|
|
conjunctionStepDay float64
|
|||
|
|
|
|||
|
|
apparentLoN func(float64, int) float64
|
|||
|
|
apparentBoN func(float64, int) float64
|
|||
|
|
apparentRaDecN func(float64, int) (float64, float64)
|
|||
|
|
semidiameterN func(float64, int) float64
|
|||
|
|
earthDistanceN func(float64, int) float64
|
|||
|
|
nodeN func(float64, int) float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type planetTransitState struct {
|
|||
|
|
jdTT float64
|
|||
|
|
separationArcsec float64
|
|||
|
|
separationSquared float64
|
|||
|
|
sunSemidiameter float64
|
|||
|
|
planetSemidiameter float64
|
|||
|
|
externalContactMetric float64
|
|||
|
|
internalContactMetric float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func mercuryTransitConfig() planetTransitConfig {
|
|||
|
|
return planetTransitConfig{
|
|||
|
|
planetIndex: 1,
|
|||
|
|
synodicPeriodDays: MERCURY_S_PERIOD,
|
|||
|
|
anchorInferiorTT: TD2UT(JDECalc(2019, 11, 11+(15+21.0/60+40.0/3600)/24), true),
|
|||
|
|
seasonWindowDays: 12,
|
|||
|
|
latitudePrefilter: 1.0,
|
|||
|
|
conjunctionStepDay: 0.00001,
|
|||
|
|
apparentLoN: MercuryApparentLoN,
|
|||
|
|
apparentBoN: MercuryApparentBoN,
|
|||
|
|
apparentRaDecN: MercuryApparentRaDecN,
|
|||
|
|
semidiameterN: MercurySemidiameterN,
|
|||
|
|
earthDistanceN: EarthMercuryAwayN,
|
|||
|
|
nodeN: MercuryAscendingNodeN,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func venusTransitConfig() planetTransitConfig {
|
|||
|
|
return planetTransitConfig{
|
|||
|
|
planetIndex: 2,
|
|||
|
|
synodicPeriodDays: VENUS_S_PERIOD,
|
|||
|
|
anchorInferiorTT: TD2UT(JDECalc(2012, 6, 6+(1+29.0/60)/24), true),
|
|||
|
|
seasonWindowDays: 8,
|
|||
|
|
latitudePrefilter: 0.8,
|
|||
|
|
conjunctionStepDay: 0.00001,
|
|||
|
|
apparentLoN: VenusApparentLoN,
|
|||
|
|
apparentBoN: VenusApparentBoN,
|
|||
|
|
apparentRaDecN: VenusApparentRaDecN,
|
|||
|
|
semidiameterN: VenusSemidiameterN,
|
|||
|
|
earthDistanceN: EarthVenusAwayN,
|
|||
|
|
nodeN: VenusAscendingNodeN,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NextMercuryTransit 返回给定时刻之后的下一次地心水星凌日。
|
|||
|
|
func NextMercuryTransit(jde float64) PlanetTransitResult {
|
|||
|
|
result, _ := searchPlanetTransit(jde, mercuryTransitConfig(), 1, false)
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// LastMercuryTransit 返回给定时刻之前的上一次地心水星凌日。
|
|||
|
|
func LastMercuryTransit(jde float64) PlanetTransitResult {
|
|||
|
|
result, _ := searchPlanetTransit(jde, mercuryTransitConfig(), -1, true)
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ClosestMercuryTransit 返回距给定时刻最近的一次地心水星凌日。
|
|||
|
|
func ClosestMercuryTransit(jde float64) PlanetTransitResult {
|
|||
|
|
return closestPlanetTransit(jde, mercuryTransitConfig())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NextVenusTransit 返回给定时刻之后的下一次地心金星凌日。
|
|||
|
|
func NextVenusTransit(jde float64) PlanetTransitResult {
|
|||
|
|
result, _ := searchPlanetTransit(jde, venusTransitConfig(), 1, false)
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// LastVenusTransit 返回给定时刻之前的上一次地心金星凌日。
|
|||
|
|
func LastVenusTransit(jde float64) PlanetTransitResult {
|
|||
|
|
result, _ := searchPlanetTransit(jde, venusTransitConfig(), -1, true)
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ClosestVenusTransit 返回距给定时刻最近的一次地心金星凌日。
|
|||
|
|
func ClosestVenusTransit(jde float64) PlanetTransitResult {
|
|||
|
|
return closestPlanetTransit(jde, venusTransitConfig())
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func closestPlanetTransit(jde float64, cfg planetTransitConfig) PlanetTransitResult {
|
|||
|
|
last, hasLast := searchPlanetTransit(jde, cfg, -1, true)
|
|||
|
|
next, hasNext := searchPlanetTransit(jde, cfg, 1, false)
|
|||
|
|
switch {
|
|||
|
|
case hasLast && !hasNext:
|
|||
|
|
return last
|
|||
|
|
case !hasLast && hasNext:
|
|||
|
|
return next
|
|||
|
|
case !hasLast && !hasNext:
|
|||
|
|
return PlanetTransitResult{}
|
|||
|
|
}
|
|||
|
|
if math.Abs(last.Greatest-jde) <= math.Abs(next.Greatest-jde) {
|
|||
|
|
return last
|
|||
|
|
}
|
|||
|
|
return next
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func searchPlanetTransit(jde float64, cfg planetTransitConfig, direction int, includeCurrent bool) (PlanetTransitResult, bool) {
|
|||
|
|
if !isFiniteFloat(jde) || direction == 0 {
|
|||
|
|
return PlanetTransitResult{}, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
targetTT := TD2UT(jde, true)
|
|||
|
|
probeTT := targetTT
|
|||
|
|
for i := 0; i < planetTransitSearchLimit; i++ {
|
|||
|
|
seasonTT, ok := nextPlanetTransitSeasonTT(probeTT, cfg, direction)
|
|||
|
|
if !ok {
|
|||
|
|
return PlanetTransitResult{}, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
seedTT := nearestPlanetTransitInferiorSeedTT(seasonTT, cfg)
|
|||
|
|
if math.Abs(seedTT-seasonTT) <= cfg.seasonWindowDays {
|
|||
|
|
conjunctionTT := refinePlanetTransitInferiorConjunctionTT(seedTT, cfg)
|
|||
|
|
if math.Abs(conjunctionTT-seasonTT) <= cfg.seasonWindowDays+1 && isPotentialPlanetTransit(conjunctionTT, cfg) {
|
|||
|
|
resultTT, ok := planetTransitAtInferiorConjunctionTT(conjunctionTT, cfg)
|
|||
|
|
if ok && planetTransitMatchesDirection(resultTT.Greatest, targetTT, direction, includeCurrent) {
|
|||
|
|
return planetTransitResultTTToUT(resultTT), true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
probeTT = seasonTT + float64(direction)*planetTransitSeasonProbeStepDays
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return PlanetTransitResult{}, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func nextPlanetTransitSeasonTT(jdTT float64, cfg planetTransitConfig, direction int) (float64, bool) {
|
|||
|
|
best := math.NaN()
|
|||
|
|
for nodeOffset := 0; nodeOffset <= 1; nodeOffset++ {
|
|||
|
|
candidate := estimatePlanetTransitSeasonTT(jdTT, cfg, nodeOffset, direction)
|
|||
|
|
candidate = refinePlanetTransitSeasonTT(candidate, cfg, nodeOffset)
|
|||
|
|
for !planetTransitMatchesDirection(candidate, jdTT, direction, false) {
|
|||
|
|
candidate += float64(direction) * planetTransitTropicalYearDays
|
|||
|
|
candidate = refinePlanetTransitSeasonTT(candidate, cfg, nodeOffset)
|
|||
|
|
}
|
|||
|
|
if !isFiniteFloat(best) || math.Abs(candidate-jdTT) < math.Abs(best-jdTT) {
|
|||
|
|
best = candidate
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if !isFiniteFloat(best) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
return best, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func estimatePlanetTransitSeasonTT(jdTT float64, cfg planetTransitConfig, nodeOffset int, direction int) float64 {
|
|||
|
|
sunLongitude := HSunApparentLoN(jdTT, planetTransitCoarseN)
|
|||
|
|
nodeLongitude := planetTransitNodeLongitude(jdTT, cfg, nodeOffset, planetTransitCoarseN)
|
|||
|
|
if direction > 0 {
|
|||
|
|
delta := Limit360(nodeLongitude - sunLongitude)
|
|||
|
|
if delta <= planetTransitSearchEpsilonDays {
|
|||
|
|
delta += 360
|
|||
|
|
}
|
|||
|
|
return jdTT + delta/planetTransitMeanSolarMotionDegPerDay
|
|||
|
|
}
|
|||
|
|
delta := Limit360(sunLongitude - nodeLongitude)
|
|||
|
|
if delta <= planetTransitSearchEpsilonDays {
|
|||
|
|
delta += 360
|
|||
|
|
}
|
|||
|
|
return jdTT - delta/planetTransitMeanSolarMotionDegPerDay
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func refinePlanetTransitSeasonTT(seedTT float64, cfg planetTransitConfig, nodeOffset int) float64 {
|
|||
|
|
current := seedTT
|
|||
|
|
for i := 0; i < 8; i++ {
|
|||
|
|
prev := current
|
|||
|
|
value := planetTransitSunNodeLongitudeDelta(prev, cfg, nodeOffset)
|
|||
|
|
slope := (planetTransitSunNodeLongitudeDelta(prev+0.5, cfg, nodeOffset) -
|
|||
|
|
planetTransitSunNodeLongitudeDelta(prev-0.5, cfg, nodeOffset)) / 1.0
|
|||
|
|
if slope == 0 || !isFiniteFloat(slope) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
current = prev - value/slope
|
|||
|
|
if math.Abs(current-prev) <= 0.00001 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return current
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitSunNodeLongitudeDelta(jdTT float64, cfg planetTransitConfig, nodeOffset int) float64 {
|
|||
|
|
return planetTransitAngleDelta(HSunApparentLoN(jdTT, planetTransitCoarseN) -
|
|||
|
|
planetTransitNodeLongitude(jdTT, cfg, nodeOffset, planetTransitCoarseN))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitNodeLongitude(jdTT float64, cfg planetTransitConfig, nodeOffset int, n int) float64 {
|
|||
|
|
return Limit360(cfg.nodeN(jdTT, n) + float64(nodeOffset)*180)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func nearestPlanetTransitInferiorSeedTT(seasonTT float64, cfg planetTransitConfig) float64 {
|
|||
|
|
k := math.Round((seasonTT - cfg.anchorInferiorTT) / cfg.synodicPeriodDays)
|
|||
|
|
return cfg.anchorInferiorTT + k*cfg.synodicPeriodDays
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func refinePlanetTransitInferiorConjunctionTT(seedTT float64, cfg planetTransitConfig) float64 {
|
|||
|
|
current := seedTT
|
|||
|
|
for i := 0; i < 4; i++ {
|
|||
|
|
prev := current
|
|||
|
|
value := planetTransitLongitudeDeltaN(prev, cfg, planetTransitCoarseN)
|
|||
|
|
slope := (planetTransitLongitudeDeltaN(prev+cfg.conjunctionStepDay, cfg, planetTransitCoarseN) -
|
|||
|
|
planetTransitLongitudeDeltaN(prev-cfg.conjunctionStepDay, cfg, planetTransitCoarseN)) / (2 * cfg.conjunctionStepDay)
|
|||
|
|
if slope == 0 || !isFiniteFloat(slope) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
current = prev - value/slope
|
|||
|
|
if math.Abs(current-prev) <= 30.0/86400.0 {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for i := 0; i < 8; i++ {
|
|||
|
|
prev := current
|
|||
|
|
value := planetTransitLongitudeDeltaN(prev, cfg, -1)
|
|||
|
|
slope := (planetTransitLongitudeDeltaN(prev+cfg.conjunctionStepDay, cfg, -1) -
|
|||
|
|
planetTransitLongitudeDeltaN(prev-cfg.conjunctionStepDay, cfg, -1)) / (2 * cfg.conjunctionStepDay)
|
|||
|
|
if slope == 0 || !isFiniteFloat(slope) {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
current = prev - value/slope
|
|||
|
|
if math.Abs(current-prev) <= cfg.conjunctionStepDay {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return current
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitLongitudeDeltaN(jdTT float64, cfg planetTransitConfig, n int) float64 {
|
|||
|
|
return planetTransitAngleDelta(cfg.apparentLoN(jdTT, n) - HSunApparentLoN(jdTT, n))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isPotentialPlanetTransit(conjunctionTT float64, cfg planetTransitConfig) bool {
|
|||
|
|
if cfg.earthDistanceN(conjunctionTT, planetTransitCoarseN) > EarthAwayN(conjunctionTT, planetTransitCoarseN) {
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
return math.Abs(cfg.apparentBoN(conjunctionTT, planetTransitCoarseN)) <= cfg.latitudePrefilter
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitAtInferiorConjunctionTT(conjunctionTT float64, cfg planetTransitConfig) (PlanetTransitResult, bool) {
|
|||
|
|
greatestTT := greatestPlanetTransitTT(conjunctionTT, cfg)
|
|||
|
|
greatestState := planetTransitStateAt(greatestTT, cfg, -1)
|
|||
|
|
if !isFiniteFloat(greatestState.externalContactMetric) || greatestState.externalContactMetric > 0 {
|
|||
|
|
return PlanetTransitResult{}, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result := PlanetTransitResult{
|
|||
|
|
Valid: true,
|
|||
|
|
PlanetIndex: cfg.planetIndex,
|
|||
|
|
Greatest: greatestTT,
|
|||
|
|
MinimumSeparationArcsec: greatestState.separationArcsec,
|
|||
|
|
SunSemidiameterArcsec: greatestState.sunSemidiameter,
|
|||
|
|
PlanetSemidiameterArcsec: greatestState.planetSemidiameter,
|
|||
|
|
HasExternal: true,
|
|||
|
|
HasInternal: greatestState.internalContactMetric <= 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
externalIngress, ok := refinePlanetTransitContactTT(greatestTT, cfg, -1, false)
|
|||
|
|
if !ok {
|
|||
|
|
return PlanetTransitResult{}, false
|
|||
|
|
}
|
|||
|
|
externalEgress, ok := refinePlanetTransitContactTT(greatestTT, cfg, 1, false)
|
|||
|
|
if !ok || externalEgress <= externalIngress {
|
|||
|
|
return PlanetTransitResult{}, false
|
|||
|
|
}
|
|||
|
|
result.ExternalIngress = externalIngress
|
|||
|
|
result.ExternalEgress = externalEgress
|
|||
|
|
|
|||
|
|
if result.HasInternal {
|
|||
|
|
internalIngress, ok := refinePlanetTransitContactTT(greatestTT, cfg, -1, true)
|
|||
|
|
if ok {
|
|||
|
|
result.InternalIngress = internalIngress
|
|||
|
|
}
|
|||
|
|
internalEgress, ok := refinePlanetTransitContactTT(greatestTT, cfg, 1, true)
|
|||
|
|
if ok && internalEgress > internalIngress {
|
|||
|
|
result.InternalEgress = internalEgress
|
|||
|
|
}
|
|||
|
|
result.HasInternal = result.InternalIngress != 0 && result.InternalEgress != 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func greatestPlanetTransitTT(seedTT float64, cfg planetTransitConfig) float64 {
|
|||
|
|
left := seedTT - planetTransitGreatestWindowDays
|
|||
|
|
right := seedTT + planetTransitGreatestWindowDays
|
|||
|
|
goldenRatio := (math.Sqrt(5) - 1) / 2
|
|||
|
|
|
|||
|
|
x1 := right - goldenRatio*(right-left)
|
|||
|
|
x2 := left + goldenRatio*(right-left)
|
|||
|
|
f1 := planetTransitStateAt(x1, cfg, planetTransitCoarseN).separationSquared
|
|||
|
|
f2 := planetTransitStateAt(x2, cfg, planetTransitCoarseN).separationSquared
|
|||
|
|
|
|||
|
|
for i := 0; i < 80 && right-left > planetTransitGreatestToleranceDays; i++ {
|
|||
|
|
if f1 <= f2 {
|
|||
|
|
right = x2
|
|||
|
|
x2 = x1
|
|||
|
|
f2 = f1
|
|||
|
|
x1 = right - goldenRatio*(right-left)
|
|||
|
|
f1 = planetTransitStateAt(x1, cfg, planetTransitCoarseN).separationSquared
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
left = x1
|
|||
|
|
x1 = x2
|
|||
|
|
f1 = f2
|
|||
|
|
x2 = left + goldenRatio*(right-left)
|
|||
|
|
f2 = planetTransitStateAt(x2, cfg, planetTransitCoarseN).separationSquared
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
center := (left + right) / 2
|
|||
|
|
left = center - 2.0/24.0
|
|||
|
|
right = center + 2.0/24.0
|
|||
|
|
x1 = right - goldenRatio*(right-left)
|
|||
|
|
x2 = left + goldenRatio*(right-left)
|
|||
|
|
f1 = planetTransitStateAt(x1, cfg, -1).separationSquared
|
|||
|
|
f2 = planetTransitStateAt(x2, cfg, -1).separationSquared
|
|||
|
|
for i := 0; i < 80 && right-left > planetTransitGreatestToleranceDays; i++ {
|
|||
|
|
if f1 <= f2 {
|
|||
|
|
right = x2
|
|||
|
|
x2 = x1
|
|||
|
|
f2 = f1
|
|||
|
|
x1 = right - goldenRatio*(right-left)
|
|||
|
|
f1 = planetTransitStateAt(x1, cfg, -1).separationSquared
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
left = x1
|
|||
|
|
x1 = x2
|
|||
|
|
f1 = f2
|
|||
|
|
x2 = left + goldenRatio*(right-left)
|
|||
|
|
f2 = planetTransitStateAt(x2, cfg, -1).separationSquared
|
|||
|
|
}
|
|||
|
|
return (left + right) / 2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitStateAt(jdTT float64, cfg planetTransitConfig, n int) planetTransitState {
|
|||
|
|
planetRA, planetDec := cfg.apparentRaDecN(jdTT, n)
|
|||
|
|
sunRA, sunDec := HSunApparentRaDecN(jdTT, n)
|
|||
|
|
separationArcsec := StarAngularSeparation(planetRA, planetDec, sunRA, sunDec) * 3600
|
|||
|
|
sunSemidiameter := SunSemidiameterN(jdTT, n)
|
|||
|
|
planetSemidiameter := cfg.semidiameterN(jdTT, n)
|
|||
|
|
return planetTransitState{
|
|||
|
|
jdTT: jdTT,
|
|||
|
|
separationArcsec: separationArcsec,
|
|||
|
|
separationSquared: separationArcsec * separationArcsec,
|
|||
|
|
sunSemidiameter: sunSemidiameter,
|
|||
|
|
planetSemidiameter: planetSemidiameter,
|
|||
|
|
externalContactMetric: separationArcsec - (sunSemidiameter + planetSemidiameter),
|
|||
|
|
internalContactMetric: separationArcsec - (sunSemidiameter - planetSemidiameter),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func refinePlanetTransitContactTT(greatestTT float64, cfg planetTransitConfig, direction int, internal bool) (float64, bool) {
|
|||
|
|
if direction != -1 && direction != 1 {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
metric := func(jdTT float64) float64 {
|
|||
|
|
state := planetTransitStateAt(jdTT, cfg, -1)
|
|||
|
|
if internal {
|
|||
|
|
return state.internalContactMetric
|
|||
|
|
}
|
|||
|
|
return state.externalContactMetric
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
nearJD := greatestTT
|
|||
|
|
nearValue := metric(nearJD)
|
|||
|
|
if !isFiniteFloat(nearValue) || nearValue > 0 {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
maxSteps := int(math.Ceil(planetTransitContactSpanDays / planetTransitContactStepDays))
|
|||
|
|
for i := 1; i <= maxSteps; i++ {
|
|||
|
|
farJD := greatestTT + float64(direction)*planetTransitContactStepDays*float64(i)
|
|||
|
|
farValue := metric(farJD)
|
|||
|
|
if !isFiniteFloat(farValue) {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if farValue >= 0 {
|
|||
|
|
return bisectPlanetTransitContactTT(nearJD, nearValue, farJD, farValue, metric)
|
|||
|
|
}
|
|||
|
|
nearJD = farJD
|
|||
|
|
nearValue = farValue
|
|||
|
|
}
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func bisectPlanetTransitContactTT(leftJD, leftValue, rightJD, rightValue float64, metric func(float64) float64) (float64, bool) {
|
|||
|
|
if leftJD > rightJD {
|
|||
|
|
leftJD, rightJD = rightJD, leftJD
|
|||
|
|
leftValue, rightValue = rightValue, leftValue
|
|||
|
|
}
|
|||
|
|
if leftValue == 0 {
|
|||
|
|
return leftJD, true
|
|||
|
|
}
|
|||
|
|
if rightValue == 0 {
|
|||
|
|
return rightJD, true
|
|||
|
|
}
|
|||
|
|
if leftValue*rightValue > 0 {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for i := 0; i < 80 && rightJD-leftJD > planetTransitContactToleranceDays; i++ {
|
|||
|
|
midJD := (leftJD + rightJD) / 2
|
|||
|
|
midValue := metric(midJD)
|
|||
|
|
if !isFiniteFloat(midValue) {
|
|||
|
|
return 0, false
|
|||
|
|
}
|
|||
|
|
if midValue == 0 {
|
|||
|
|
return midJD, true
|
|||
|
|
}
|
|||
|
|
if leftValue*midValue <= 0 {
|
|||
|
|
rightJD = midJD
|
|||
|
|
rightValue = midValue
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
leftJD = midJD
|
|||
|
|
leftValue = midValue
|
|||
|
|
}
|
|||
|
|
return (leftJD + rightJD) / 2, true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitResultTTToUT(result PlanetTransitResult) PlanetTransitResult {
|
|||
|
|
result.Greatest = TD2UT(result.Greatest, false)
|
|||
|
|
result.ExternalIngress = TD2UT(result.ExternalIngress, false)
|
|||
|
|
result.ExternalEgress = TD2UT(result.ExternalEgress, false)
|
|||
|
|
if result.InternalIngress != 0 {
|
|||
|
|
result.InternalIngress = TD2UT(result.InternalIngress, false)
|
|||
|
|
}
|
|||
|
|
if result.InternalEgress != 0 {
|
|||
|
|
result.InternalEgress = TD2UT(result.InternalEgress, false)
|
|||
|
|
}
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitMatchesDirection(eventJDE, targetJDE float64, direction int, includeCurrent bool) bool {
|
|||
|
|
delta := eventJDE - targetJDE
|
|||
|
|
if math.Abs(delta) <= planetTransitSearchEpsilonDays {
|
|||
|
|
return includeCurrent
|
|||
|
|
}
|
|||
|
|
if direction > 0 {
|
|||
|
|
return delta > 0
|
|||
|
|
}
|
|||
|
|
return delta < 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func planetTransitAngleDelta(diff float64) float64 {
|
|||
|
|
diff = Limit360(diff)
|
|||
|
|
if diff > 180 {
|
|||
|
|
diff -= 360
|
|||
|
|
}
|
|||
|
|
if diff < -180 {
|
|||
|
|
diff += 360
|
|||
|
|
}
|
|||
|
|
return diff
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isFiniteFloat(value float64) bool {
|
|||
|
|
return !math.IsNaN(value) && !math.IsInf(value, 0)
|
|||
|
|
}
|