fix: 修正行星事件边界与留点计算
- 统一 UT 事件时刻与 TT 查询时刻的边界判断 - 将外行星留点搜索锚定到对应冲日周期 - 修正水星、金星合日、留、大距事件选择 - 统一七大行星视位置计算辅助逻辑 - 增加公开 Last/Next 边界和 JPL/NAOJ 基线回归测试
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package basic
|
||||
|
||||
import "math"
|
||||
|
||||
const exactEventTolerance = 2.0 / 86400.0
|
||||
|
||||
func sameEventJD(a, b float64) bool {
|
||||
return math.Abs(a-b) <= exactEventTolerance
|
||||
}
|
||||
|
||||
func sameEventUTQueryTT(eventUT, queryTT float64) bool {
|
||||
return math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) <= exactEventTolerance
|
||||
}
|
||||
|
||||
func closestEventUTToQueryTT(queryTT, best float64, candidates ...float64) float64 {
|
||||
bestAbs := math.Abs(eventUTQueryTTDelta(best, queryTT))
|
||||
for _, candidate := range candidates {
|
||||
candidateAbs := math.Abs(eventUTQueryTTDelta(candidate, queryTT))
|
||||
if candidateAbs < bestAbs {
|
||||
best = candidate
|
||||
bestAbs = candidateAbs
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
type phaseEventSearchFunc func(jde, degree float64, next uint8) float64
|
||||
type simpleEventSearchFunc func(jde float64) float64
|
||||
|
||||
func inclusiveLastPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
|
||||
last := fn(jde, degree, 0)
|
||||
next := fn(jde, degree, 1)
|
||||
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
|
||||
return next
|
||||
}
|
||||
if eventUTQueryBeforeOrEqual(last, jde) {
|
||||
return last
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
func inclusiveNextPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
|
||||
last := fn(jde, degree, 0)
|
||||
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
|
||||
return last
|
||||
}
|
||||
next := fn(jde, degree, 1)
|
||||
if eventUTQueryAfterOrEqual(next, jde) {
|
||||
return next
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
func inclusiveLastSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
|
||||
last := lastFn(jde)
|
||||
next := nextFn(jde)
|
||||
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
|
||||
return next
|
||||
}
|
||||
if eventUTQueryBeforeOrEqual(last, jde) {
|
||||
return last
|
||||
}
|
||||
return last
|
||||
}
|
||||
|
||||
func inclusiveNextSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
|
||||
last := lastFn(jde)
|
||||
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
|
||||
return last
|
||||
}
|
||||
next := nextFn(jde)
|
||||
if eventUTQueryAfterOrEqual(next, jde) {
|
||||
return next
|
||||
}
|
||||
return next
|
||||
}
|
||||
@@ -40,9 +40,7 @@ func eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal fl
|
||||
return 0, 0, 0, 0, false
|
||||
}
|
||||
|
||||
// eventZeroRefine 细化 seed 附近的零点;若找不到可用括号区间,则退回旧的固定步长扫描。
|
||||
// eventZeroRefine refines a nearby zero crossing and falls back to the legacy
|
||||
// fixed-step scan when no usable bracket is found.
|
||||
// eventZeroRefine 细化 seed 附近的零点;无可用括号区间时退回固定步长扫描。
|
||||
func eventZeroRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||
leftJD := seed - halfWindow
|
||||
centerJD := seed
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package basic
|
||||
|
||||
import "math"
|
||||
|
||||
const innerEventEpsilon = 4.0 / 86400.0
|
||||
|
||||
func eventQueryTTAsUT(queryTT float64) float64 {
|
||||
return TD2UT(queryTT, false)
|
||||
}
|
||||
|
||||
func eventUTQueryTTDelta(eventUT, queryTT float64) float64 {
|
||||
return eventUT - eventQueryTTAsUT(queryTT)
|
||||
}
|
||||
|
||||
func eventUTQueryBeforeOrEqual(eventUT, queryTT float64) bool {
|
||||
return eventUTQueryTTDelta(eventUT, queryTT) <= innerEventEpsilon
|
||||
}
|
||||
|
||||
func eventUTQueryAfterOrEqual(eventUT, queryTT float64) bool {
|
||||
return eventUTQueryTTDelta(eventUT, queryTT) >= -innerEventEpsilon
|
||||
}
|
||||
|
||||
func eventUTNextQueryTT(eventUT float64) float64 {
|
||||
return TD2UT(eventUT, true) + 1.0
|
||||
}
|
||||
|
||||
func eventUTLastQueryTT(eventUT float64) float64 {
|
||||
return TD2UT(eventUT, true) - 1.0
|
||||
}
|
||||
|
||||
func innerNextCycleOffset(delta, period float64) float64 {
|
||||
if delta <= 0 {
|
||||
return -delta * period / 360.0
|
||||
}
|
||||
return (360.0 - delta) * period / 360.0
|
||||
}
|
||||
|
||||
func innerLastCycleOffset(delta, period float64) float64 {
|
||||
if delta >= 0 {
|
||||
return delta * period / 360.0
|
||||
}
|
||||
return (360.0 + delta) * period / 360.0
|
||||
}
|
||||
|
||||
func clampFloat64(v, min, max float64) float64 {
|
||||
if v < min {
|
||||
return min
|
||||
}
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func scanWindowForMinAbs(start, end, step float64, fn func(float64) float64) float64 {
|
||||
if end < start {
|
||||
start, end = end, start
|
||||
}
|
||||
if step <= 0 || end == start {
|
||||
return start
|
||||
}
|
||||
bestJD := start
|
||||
bestAbs := math.Abs(fn(start))
|
||||
for jd := start + step; jd < end; jd += step {
|
||||
candidateAbs := math.Abs(fn(jd))
|
||||
if candidateAbs < bestAbs {
|
||||
bestAbs = candidateAbs
|
||||
bestJD = jd
|
||||
}
|
||||
}
|
||||
endAbs := math.Abs(fn(end))
|
||||
if endAbs < bestAbs {
|
||||
return end
|
||||
}
|
||||
return bestJD
|
||||
}
|
||||
|
||||
func scanWindowForMax(start, end, step float64, fn func(float64) float64) float64 {
|
||||
if end < start {
|
||||
start, end = end, start
|
||||
}
|
||||
if step <= 0 || end == start {
|
||||
return start
|
||||
}
|
||||
bestJD := start
|
||||
bestVal := fn(start)
|
||||
for jd := start + step; jd < end; jd += step {
|
||||
candidateVal := fn(jd)
|
||||
if candidateVal > bestVal {
|
||||
bestVal = candidateVal
|
||||
bestJD = jd
|
||||
}
|
||||
}
|
||||
endVal := fn(end)
|
||||
if endVal > bestVal {
|
||||
return end
|
||||
}
|
||||
return bestJD
|
||||
}
|
||||
|
||||
func boundedEventZeroRefine(seed, start, end, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||
if end < start {
|
||||
start, end = end, start
|
||||
}
|
||||
if end <= start {
|
||||
return start
|
||||
}
|
||||
maxHalfWindow := (end - start) / 2
|
||||
if halfWindow > maxHalfWindow {
|
||||
halfWindow = maxHalfWindow
|
||||
}
|
||||
if halfWindow <= 0 {
|
||||
return clampFloat64(seed, start, end)
|
||||
}
|
||||
seed = clampFloat64(seed, start+halfWindow, end-halfWindow)
|
||||
return eventZeroRefine(seed, halfWindow, step, fn)
|
||||
}
|
||||
|
||||
func zeroEventInWindow(start, end, coarseStep, halfWindow, refineStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
||||
if end < start {
|
||||
start, end = end, start
|
||||
}
|
||||
if end <= start {
|
||||
return start
|
||||
}
|
||||
rangeDays := end - start
|
||||
if coarseStep <= 0 || coarseStep > rangeDays {
|
||||
coarseStep = rangeDays / 6.0
|
||||
}
|
||||
if coarseStep < 0.5 {
|
||||
coarseStep = 0.5
|
||||
}
|
||||
if refineStep <= 0 {
|
||||
refineStep = 0.5 / 86400.0
|
||||
}
|
||||
if halfWindow <= 0 {
|
||||
halfWindow = coarseStep
|
||||
}
|
||||
guess := scanWindowForMinAbs(start, end, coarseStep, coarseFn)
|
||||
return boundedEventZeroRefine(guess, start, end, halfWindow, refineStep, exactFn)
|
||||
}
|
||||
|
||||
func maximizeInWindow(start, end, coarseStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
||||
if end < start {
|
||||
start, end = end, start
|
||||
}
|
||||
if end <= start {
|
||||
return start
|
||||
}
|
||||
rangeDays := end - start
|
||||
if coarseStep <= 0 || coarseStep > rangeDays {
|
||||
coarseStep = rangeDays / 6.0
|
||||
}
|
||||
if coarseStep < 0.5 {
|
||||
coarseStep = 0.5
|
||||
}
|
||||
guess := scanWindowForMax(start, end, coarseStep, coarseFn)
|
||||
left := clampFloat64(guess-coarseStep, start, end)
|
||||
right := clampFloat64(guess+coarseStep, start, end)
|
||||
if right-left <= innerEventEpsilon {
|
||||
return guess
|
||||
}
|
||||
for i := 0; i < 20; i++ {
|
||||
third := (right - left) / 3.0
|
||||
leftThird := left + third
|
||||
rightThird := right - third
|
||||
if exactFn(leftThird) <= exactFn(rightThird) {
|
||||
left = leftThird
|
||||
continue
|
||||
}
|
||||
right = rightThird
|
||||
}
|
||||
bestJD := guess
|
||||
bestVal := exactFn(bestJD)
|
||||
for _, jd := range []float64{left, (left + right) / 2.0, right} {
|
||||
candidateVal := exactFn(jd)
|
||||
if candidateVal > bestVal {
|
||||
bestVal = candidateVal
|
||||
bestJD = jd
|
||||
}
|
||||
}
|
||||
return bestJD
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package basic
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInnerPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
seed float64
|
||||
lastFn func(float64) float64
|
||||
nextFn func(float64) float64
|
||||
}{
|
||||
{name: "MercuryConjunction", seed: NextMercuryConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryConjunction, nextFn: NextMercuryConjunction},
|
||||
{name: "MercuryInferior", seed: NextMercuryInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryInferiorConjunctionInclusive, nextFn: NextMercuryInferiorConjunctionInclusive},
|
||||
{name: "MercurySuperior", seed: NextMercurySuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercurySuperiorConjunctionInclusive, nextFn: NextMercurySuperiorConjunctionInclusive},
|
||||
{name: "MercuryRetrograde", seed: NextMercuryRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeInclusive, nextFn: NextMercuryRetrogradeInclusive},
|
||||
{name: "MercuryP2R", seed: NextMercuryProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryProgradeToRetrogradeInclusive, nextFn: NextMercuryProgradeToRetrogradeInclusive},
|
||||
{name: "MercuryR2P", seed: NextMercuryRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeToProgradeInclusive, nextFn: NextMercuryRetrogradeToProgradeInclusive},
|
||||
{name: "MercuryGreatestElongation", seed: NextMercuryGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationInclusive, nextFn: NextMercuryGreatestElongationInclusive},
|
||||
{name: "MercuryEastElongation", seed: NextMercuryGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationEastInclusive, nextFn: NextMercuryGreatestElongationEastInclusive},
|
||||
{name: "MercuryWestElongation", seed: NextMercuryGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationWestInclusive, nextFn: NextMercuryGreatestElongationWestInclusive},
|
||||
{name: "VenusConjunction", seed: NextVenusConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusConjunction, nextFn: NextVenusConjunction},
|
||||
{name: "VenusInferior", seed: NextVenusInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusInferiorConjunctionInclusive, nextFn: NextVenusInferiorConjunctionInclusive},
|
||||
{name: "VenusSuperior", seed: NextVenusSuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusSuperiorConjunctionInclusive, nextFn: NextVenusSuperiorConjunctionInclusive},
|
||||
{name: "VenusRetrograde", seed: NextVenusRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeInclusive, nextFn: NextVenusRetrogradeInclusive},
|
||||
{name: "VenusP2R", seed: NextVenusProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusProgradeToRetrogradeInclusive, nextFn: NextVenusProgradeToRetrogradeInclusive},
|
||||
{name: "VenusR2P", seed: NextVenusRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeToProgradeInclusive, nextFn: NextVenusRetrogradeToProgradeInclusive},
|
||||
{name: "VenusGreatestElongation", seed: NextVenusGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationInclusive, nextFn: NextVenusGreatestElongationInclusive},
|
||||
{name: "VenusEastElongation", seed: NextVenusGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationEastInclusive, nextFn: NextVenusGreatestElongationEastInclusive},
|
||||
{name: "VenusWestElongation", seed: NextVenusGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationWestInclusive, nextFn: NextVenusGreatestElongationWestInclusive},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
queryTT := TD2UT(tc.seed, true)
|
||||
last := tc.lastFn(queryTT)
|
||||
next := tc.nextFn(queryTT)
|
||||
if !sameEventJD(last, tc.seed) {
|
||||
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||
}
|
||||
if !sameEventJD(next, tc.seed) {
|
||||
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type innerBaselineFile struct {
|
||||
Events []innerBaselineEvent `json:"events"`
|
||||
}
|
||||
|
||||
type innerBaselineEvent struct {
|
||||
Planet string `json:"planet"`
|
||||
Kind string `json:"kind"`
|
||||
NAOJHintJST string `json:"naoj_hint_jst"`
|
||||
Precision string `json:"precision"`
|
||||
CandidateJST string `json:"candidate_jst"`
|
||||
VerifiedJST string `json:"verified_jst"`
|
||||
CandidateSource string `json:"candidate_source"`
|
||||
}
|
||||
|
||||
func loadInnerBaseline(t *testing.T) innerBaselineFile {
|
||||
t.Helper()
|
||||
|
||||
paths := [][]string{
|
||||
{
|
||||
"testdata/jpl_inner_event_baseline.json",
|
||||
"basic/testdata/jpl_inner_event_baseline.json",
|
||||
},
|
||||
{
|
||||
"testdata/jpl_inner_event_baseline_21c_sample.json",
|
||||
"basic/testdata/jpl_inner_event_baseline_21c_sample.json",
|
||||
},
|
||||
{
|
||||
"testdata/jpl_inner_event_baseline_20c_sample.json",
|
||||
"basic/testdata/jpl_inner_event_baseline_20c_sample.json",
|
||||
},
|
||||
{
|
||||
"testdata/jpl_inner_event_baseline_22c_sample.json",
|
||||
"basic/testdata/jpl_inner_event_baseline_22c_sample.json",
|
||||
},
|
||||
}
|
||||
var merged innerBaselineFile
|
||||
for index, candidates := range paths {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
for _, path := range candidates {
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
var baseline innerBaselineFile
|
||||
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
merged.Events = append(merged.Events, baseline.Events...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil && index == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(merged.Events) == 0 {
|
||||
t.Fatal("empty inner baseline file")
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func parseInnerBaselineTime(t *testing.T, value string) time.Time {
|
||||
t.Helper()
|
||||
loc := time.FixedZone("JST", 9*3600)
|
||||
layouts := []string{
|
||||
"2006-01-02 15:04:05 MST",
|
||||
"2006-01-02 15:04 MST",
|
||||
"2006-01-02 15:04:05",
|
||||
"2006-01-02 15:04",
|
||||
}
|
||||
var err error
|
||||
for _, layout := range layouts {
|
||||
when, parseErr := time.ParseInLocation(layout, value, loc)
|
||||
if parseErr == nil {
|
||||
return when
|
||||
}
|
||||
err = parseErr
|
||||
}
|
||||
t.Fatalf("parse baseline time %q: %v", value, err)
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func innerBaselineTolerance(event innerBaselineEvent) time.Duration {
|
||||
switch event.Kind {
|
||||
case "IC", "SC", "P2R", "R2P":
|
||||
return 2 * time.Minute
|
||||
case "GEE", "GEW":
|
||||
return 90 * time.Minute
|
||||
default:
|
||||
return 2 * time.Minute
|
||||
}
|
||||
}
|
||||
|
||||
func innerEventFuncs(t *testing.T, event innerBaselineEvent) (func(float64) float64, func(float64) float64) {
|
||||
t.Helper()
|
||||
switch event.Planet + ":" + event.Kind {
|
||||
case "Mercury:IC":
|
||||
return LastMercuryInferiorConjunctionInclusive, NextMercuryInferiorConjunctionInclusive
|
||||
case "Mercury:SC":
|
||||
return LastMercurySuperiorConjunctionInclusive, NextMercurySuperiorConjunctionInclusive
|
||||
case "Mercury:P2R":
|
||||
return LastMercuryProgradeToRetrogradeInclusive, NextMercuryProgradeToRetrogradeInclusive
|
||||
case "Mercury:R2P":
|
||||
return LastMercuryRetrogradeToProgradeInclusive, NextMercuryRetrogradeToProgradeInclusive
|
||||
case "Mercury:GEE":
|
||||
return LastMercuryGreatestElongationEastInclusive, NextMercuryGreatestElongationEastInclusive
|
||||
case "Mercury:GEW":
|
||||
return LastMercuryGreatestElongationWestInclusive, NextMercuryGreatestElongationWestInclusive
|
||||
case "Venus:IC":
|
||||
return LastVenusInferiorConjunctionInclusive, NextVenusInferiorConjunctionInclusive
|
||||
case "Venus:SC":
|
||||
return LastVenusSuperiorConjunctionInclusive, NextVenusSuperiorConjunctionInclusive
|
||||
case "Venus:P2R":
|
||||
return LastVenusProgradeToRetrogradeInclusive, NextVenusProgradeToRetrogradeInclusive
|
||||
case "Venus:R2P":
|
||||
return LastVenusRetrogradeToProgradeInclusive, NextVenusRetrogradeToProgradeInclusive
|
||||
case "Venus:GEE":
|
||||
return LastVenusGreatestElongationEastInclusive, NextVenusGreatestElongationEastInclusive
|
||||
case "Venus:GEW":
|
||||
return LastVenusGreatestElongationWestInclusive, NextVenusGreatestElongationWestInclusive
|
||||
default:
|
||||
t.Fatalf("unsupported event %s:%s", event.Planet, event.Kind)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func assertInnerBaselineEvent(t *testing.T, event innerBaselineEvent, lastFn, nextFn func(float64) float64) {
|
||||
t.Helper()
|
||||
when := parseInnerBaselineTime(t, event.VerifiedJST)
|
||||
before := when.Add(-24 * time.Hour)
|
||||
after := when.Add(24 * time.Hour)
|
||||
next := JDE2DateByZone(nextFn(toUTJD(before)), when.Location(), false)
|
||||
last := JDE2DateByZone(lastFn(toUTJD(after)), when.Location(), false)
|
||||
tolerance := innerBaselineTolerance(event)
|
||||
|
||||
if diff := next.Sub(when); diff < -tolerance || diff > tolerance {
|
||||
t.Fatalf("%s %s next mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, next, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||
}
|
||||
if diff := last.Sub(when); diff < -tolerance || diff > tolerance {
|
||||
t.Fatalf("%s %s last mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, last, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInnerPlanetTruthAgainstJPL(t *testing.T) {
|
||||
baseline := loadInnerBaseline(t)
|
||||
for _, event := range baseline.Events {
|
||||
event := event
|
||||
name := strings.Join([]string{event.Planet, event.Kind, event.VerifiedJST}, "_")
|
||||
t.Run(name, func(t *testing.T) {
|
||||
lastFn, nextFn := innerEventFuncs(t, event)
|
||||
assertInnerBaselineEvent(t, event, lastFn, nextFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
+7
-38
@@ -87,53 +87,22 @@ func JupiterApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthJupiterAway(jd float64) float64 {
|
||||
x, y, z := AJupiterXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(4, jd, -1)
|
||||
}
|
||||
|
||||
func JupiterApparentLo(jd float64) float64 {
|
||||
x, y, z := AJupiterXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AJupiterXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func JupiterApparentBo(jd float64) float64 {
|
||||
x, y, z := AJupiterXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AJupiterXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func JupiterApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := AJupiterXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AJupiterXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func JupiterMag(jd float64) float64 {
|
||||
|
||||
+66
-65
@@ -40,6 +40,28 @@ func jupiterSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64
|
||||
return sub
|
||||
}
|
||||
|
||||
func jupiterRADerivative(jde, delta float64) float64 {
|
||||
sub := JupiterApparentRa(jde+delta) - JupiterApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func jupiterRADerivativeN(jde, delta float64, n int) float64 {
|
||||
sub := JupiterApparentRaN(jde+delta, n) - JupiterApparentRaN(jde-delta, n)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func jupiterConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||
//0=last 1=next
|
||||
daysPerDegree := JUPITER_S_PERIOD / 360
|
||||
@@ -94,113 +116,92 @@ func jupiterConjunction(jde, degree float64, next uint8) float64 {
|
||||
}
|
||||
|
||||
func LastJupiterConjunction(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 0, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 0, jupiterConjunction)
|
||||
}
|
||||
|
||||
func NextJupiterConjunction(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 0, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 0, jupiterConjunction)
|
||||
}
|
||||
|
||||
func LastJupiterOpposition(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 180, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 180, jupiterConjunction)
|
||||
}
|
||||
|
||||
func NextJupiterOpposition(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 180, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 180, jupiterConjunction)
|
||||
}
|
||||
|
||||
func NextJupiterEasternQuadrature(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 90, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 90, jupiterConjunction)
|
||||
}
|
||||
|
||||
func LastJupiterEasternQuadrature(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 90, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 90, jupiterConjunction)
|
||||
}
|
||||
|
||||
func NextJupiterWesternQuadrature(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 270, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 270, jupiterConjunction)
|
||||
}
|
||||
|
||||
func LastJupiterWesternQuadrature(jde float64) float64 {
|
||||
return jupiterConjunction(jde, 270, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 270, jupiterConjunction)
|
||||
}
|
||||
|
||||
func jupiterRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
||||
//0=last 1=next
|
||||
raRate := func(jde float64, delta float64) float64 {
|
||||
sub := JupiterApparentRa(jde+delta) - JupiterApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
jde = jupiterConjunctionFull(jde, 180, 1)
|
||||
func jupiterRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||
oppositionTT := TD2UT(oppositionJD, true)
|
||||
startTT := oppositionTT
|
||||
endTT := oppositionTT
|
||||
if searchBeforeOpposition {
|
||||
jde -= 60
|
||||
easternQuadratureUT := jupiterConjunction(oppositionTT, 90, 0)
|
||||
startTT = TD2UT(easternQuadratureUT, true)
|
||||
} else {
|
||||
jde += 60
|
||||
westernQuadratureUT := jupiterConjunction(oppositionTT, 270, 1)
|
||||
endTT = TD2UT(westernQuadratureUT, true)
|
||||
}
|
||||
for {
|
||||
currentRate := raRate(jde, 1.0/86400.0)
|
||||
if math.Abs(currentRate) > 0.55 {
|
||||
jde += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
estimateJD := jde
|
||||
for {
|
||||
prevJD := estimateJD
|
||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
estimateJD = prevJD - rateValue/rateSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||
return raRate(jd, 0.5/86400.0)
|
||||
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||
return jupiterRADerivativeN(jd, 1.0/86400.0, jupiterEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return jupiterRADerivative(jd, 0.5/86400.0)
|
||||
})
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func NextJupiterRetrogradeToPrograde(jde float64) float64 {
|
||||
date := jupiterRetrograde(jde, false)
|
||||
if date < jde {
|
||||
oppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||
return jupiterRetrograde(oppositionJD+10, false)
|
||||
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||
date := jupiterRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||
return jupiterRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||
}
|
||||
|
||||
func LastJupiterRetrogradeToPrograde(jde float64) float64 {
|
||||
jde = jupiterConjunctionFull(jde, 180, 0) - 10
|
||||
date := jupiterRetrograde(jde, false)
|
||||
if date > jde {
|
||||
oppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||
return jupiterRetrograde(oppositionJD-10, false)
|
||||
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||
date := jupiterRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
previousOppositionJD := jupiterConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||
return jupiterRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||
}
|
||||
|
||||
func NextJupiterProgradeToRetrograde(jde float64) float64 {
|
||||
date := jupiterRetrograde(jde, true)
|
||||
if date < jde {
|
||||
oppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||
return jupiterRetrograde(oppositionJD+10, true)
|
||||
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||
date := jupiterRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
followingOppositionJD := jupiterConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||
return jupiterRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||
}
|
||||
|
||||
func LastJupiterProgradeToRetrograde(jde float64) float64 {
|
||||
jde = jupiterConjunctionFull(jde, 180, 0) - 10
|
||||
date := jupiterRetrograde(jde, true)
|
||||
if date > jde {
|
||||
oppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||
return jupiterRetrograde(oppositionJD-10, true)
|
||||
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||
date := jupiterRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||
return jupiterRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||
}
|
||||
|
||||
+11
-51
@@ -87,72 +87,32 @@ func MarsApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthMarsAway(jd float64) float64 {
|
||||
x, y, z := AMarsXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(3, jd, -1)
|
||||
}
|
||||
|
||||
func MarsApparentLo(jd float64) float64 {
|
||||
x, y, z := AMarsXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
//bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180.0 / math.Pi
|
||||
//bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo) + Nutation2000Bi(jd)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func MarsApparentBo(jd float64) float64 {
|
||||
x, y, z := AMarsXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func MarsApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := AMarsXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo -= GXCLo(lo, bo, jd) / 3600
|
||||
//bo += GXCBo(lo, bo, jd)
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func MarsTrueLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := AMarsXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
return lo, bo
|
||||
geo, _ := planetTrueGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func MarsTrueLo(jd float64) float64 {
|
||||
x, y, _ := AMarsXYZ(jd)
|
||||
lo := math.Atan2(y, x)
|
||||
lo = lo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
return lo
|
||||
geo, _ := planetTrueGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func MarsMag(jd float64) float64 {
|
||||
|
||||
+54
-29
@@ -116,40 +116,39 @@ func marsConjunction(jde, degree float64, next uint8) float64 {
|
||||
}
|
||||
|
||||
func LastMarsConjunction(jde float64) float64 {
|
||||
return marsConjunction(jde, 0, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 0, marsConjunction)
|
||||
}
|
||||
|
||||
func NextMarsConjunction(jde float64) float64 {
|
||||
return marsConjunction(jde, 0, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 0, marsConjunction)
|
||||
}
|
||||
|
||||
func LastMarsOpposition(jde float64) float64 {
|
||||
return marsConjunction(jde, 180, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 180, marsConjunction)
|
||||
}
|
||||
|
||||
func NextMarsOpposition(jde float64) float64 {
|
||||
return marsConjunction(jde, 180, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 180, marsConjunction)
|
||||
}
|
||||
|
||||
func NextMarsEasternQuadrature(jde float64) float64 {
|
||||
return marsConjunction(jde, 90, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 90, marsConjunction)
|
||||
}
|
||||
|
||||
func LastMarsEasternQuadrature(jde float64) float64 {
|
||||
return marsConjunction(jde, 90, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 90, marsConjunction)
|
||||
}
|
||||
|
||||
func NextMarsWesternQuadrature(jde float64) float64 {
|
||||
return marsConjunction(jde, 270, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 270, marsConjunction)
|
||||
}
|
||||
|
||||
func LastMarsWesternQuadrature(jde float64) float64 {
|
||||
return marsConjunction(jde, 270, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 270, marsConjunction)
|
||||
}
|
||||
|
||||
func marsRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
||||
//0=last 1=next
|
||||
jde = marsConjunctionFull(jde, 180, 1)
|
||||
func marsRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||
jde := oppositionJD
|
||||
if searchBeforeOpposition {
|
||||
jde -= 60
|
||||
} else {
|
||||
@@ -179,40 +178,66 @@ func marsRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func marsOppositionFromBefore(oppositionJD float64) float64 {
|
||||
return marsConjunctionFull(eventUTLastQueryTT(oppositionJD), 180, 1)
|
||||
}
|
||||
|
||||
func marsOppositionFromAfter(oppositionJD float64) float64 {
|
||||
return marsConjunctionFull(eventUTNextQueryTT(oppositionJD), 180, 0)
|
||||
}
|
||||
|
||||
func NextMarsRetrogradeToPrograde(jde float64) float64 {
|
||||
date := marsRetrograde(jde, false)
|
||||
if date < jde {
|
||||
oppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||
return marsRetrograde(oppositionJD+10, false)
|
||||
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||
date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) {
|
||||
sameOppositionJD := marsOppositionFromBefore(lastOppositionJD)
|
||||
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||
}
|
||||
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||
return marsRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastMarsRetrogradeToPrograde(jde float64) float64 {
|
||||
jde = marsConjunctionFull(jde, 180, 0) - 10
|
||||
date := marsRetrograde(jde, false)
|
||||
if date > jde {
|
||||
oppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||
return marsRetrograde(oppositionJD-10, false)
|
||||
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||
date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) {
|
||||
sameOppositionJD := marsOppositionFromBefore(lastOppositionJD)
|
||||
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||
}
|
||||
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||
previousOppositionJD := marsConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||
return marsRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func NextMarsProgradeToRetrograde(jde float64) float64 {
|
||||
date := marsRetrograde(jde, true)
|
||||
if date < jde {
|
||||
oppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||
return marsRetrograde(oppositionJD+10, true)
|
||||
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||
date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) {
|
||||
sameOppositionJD := marsOppositionFromAfter(nextOppositionJD)
|
||||
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||
}
|
||||
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||
followingOppositionJD := marsConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||
return marsRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastMarsProgradeToRetrograde(jde float64) float64 {
|
||||
jde = marsConjunctionFull(jde, 180, 0) - 10
|
||||
date := marsRetrograde(jde, true)
|
||||
if date > jde {
|
||||
oppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||
return marsRetrograde(oppositionJD-10, true)
|
||||
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||
date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) {
|
||||
sameOppositionJD := marsOppositionFromAfter(nextOppositionJD)
|
||||
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||
}
|
||||
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||
return marsRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
+7
-37
@@ -80,52 +80,22 @@ func MercuryApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthMercuryAway(jd float64) float64 {
|
||||
x, y, z := AMercuryXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(1, jd, -1)
|
||||
}
|
||||
|
||||
func MercuryApparentLo(jd float64) float64 {
|
||||
x, y, z := AMercuryXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMercuryXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func MercuryApparentBo(jd float64) float64 {
|
||||
x, y, z := AMercuryXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMercuryXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func MercuryApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := AMercuryXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMercuryXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo) + Nutation2000Bi(jd)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func MercuryMag(jd float64) float64 {
|
||||
|
||||
+268
-90
@@ -11,6 +11,7 @@ const (
|
||||
MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004))
|
||||
mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0
|
||||
mercuryLightTimeDaysPerAU = 0.0057755183
|
||||
mercuryEventSearchN = 16
|
||||
)
|
||||
|
||||
type mercuryConjunctionLBR struct {
|
||||
@@ -206,41 +207,49 @@ func mercuryConjunction(jde float64, next uint8) float64 {
|
||||
}
|
||||
|
||||
func LastMercuryConjunction(jde float64) float64 {
|
||||
return mercuryConjunction(jde, 0)
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
|
||||
}
|
||||
|
||||
func NextMercuryConjunction(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
|
||||
}
|
||||
|
||||
func LastMercuryConjunctionStrict(jde float64) float64 {
|
||||
return mercuryConjunction(jde, 0)
|
||||
}
|
||||
|
||||
func NextMercuryConjunctionStrict(jde float64) float64 {
|
||||
return mercuryConjunction(jde, 1)
|
||||
}
|
||||
|
||||
func NextMercuryInferiorConjunction(jde float64) float64 {
|
||||
date := NextMercuryConjunction(jde)
|
||||
date := NextMercuryConjunctionStrict(jde)
|
||||
if EarthMercuryAway(date) > EarthAway(date) {
|
||||
return NextMercuryConjunction(date + 2)
|
||||
return NextMercuryConjunctionStrict(date + 2)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func NextMercurySuperiorConjunction(jde float64) float64 {
|
||||
date := NextMercuryConjunction(jde)
|
||||
date := NextMercuryConjunctionStrict(jde)
|
||||
if EarthMercuryAway(date) < EarthAway(date) {
|
||||
return NextMercuryConjunction(date + 2)
|
||||
return NextMercuryConjunctionStrict(date + 2)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastMercuryInferiorConjunction(jde float64) float64 {
|
||||
date := LastMercuryConjunction(jde)
|
||||
date := LastMercuryConjunctionStrict(jde)
|
||||
if EarthMercuryAway(date) > EarthAway(date) {
|
||||
return LastMercuryConjunction(date - 2)
|
||||
return LastMercuryConjunctionStrict(date - 2)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastMercurySuperiorConjunction(jde float64) float64 {
|
||||
date := LastMercuryConjunction(jde)
|
||||
date := LastMercuryConjunctionStrict(jde)
|
||||
if EarthMercuryAway(date) < EarthAway(date) {
|
||||
return LastMercuryConjunction(date - 2)
|
||||
return LastMercuryConjunctionStrict(date - 2)
|
||||
}
|
||||
return date
|
||||
}
|
||||
@@ -257,16 +266,6 @@ func mercuryRetrograde(jde float64) float64 {
|
||||
}
|
||||
return sub
|
||||
}
|
||||
raRate := func(jde float64, delta float64) float64 {
|
||||
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
||||
currentRADelta := solarRADelta(jde)
|
||||
@@ -276,7 +275,7 @@ func mercuryRetrograde(jde float64) float64 {
|
||||
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5)
|
||||
}
|
||||
for {
|
||||
currentRate := raRate(jde, 1.0/86400.0)
|
||||
currentRate := mercuryRADerivative(jde, 1.0/86400.0)
|
||||
if math.Abs(currentRate) > 0.55 {
|
||||
jde += 2
|
||||
continue
|
||||
@@ -286,82 +285,212 @@ func mercuryRetrograde(jde float64) float64 {
|
||||
estimateJD := jde
|
||||
for {
|
||||
prevJD := estimateJD
|
||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
rateValue := mercuryRADerivative(prevJD, 2.0/86400.0)
|
||||
rateSlope := (mercuryRADerivative(prevJD+15.0/86400.0, 2.0/86400.0) - mercuryRADerivative(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
estimateJD = prevJD - rateValue/rateSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||
return raRate(jd, 0.5/86400.0)
|
||||
return mercuryRADerivative(jd, 0.5/86400.0)
|
||||
})
|
||||
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func mercuryRADerivative(jde, delta float64) float64 {
|
||||
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func mercuryStationIsProgradeToRetrograde(eventUT float64) bool {
|
||||
for _, offset := range []float64{0.25, 0.5, 1.0} {
|
||||
before := mercuryRADerivative(eventUT-offset, 0.5/86400.0)
|
||||
after := mercuryRADerivative(eventUT+offset, 0.5/86400.0)
|
||||
if before > 0 && after < 0 {
|
||||
return true
|
||||
}
|
||||
if before < 0 && after > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
before := mercuryRADerivative(eventUT-0.25, 0.5/86400.0)
|
||||
after := mercuryRADerivative(eventUT+0.25, 0.5/86400.0)
|
||||
return before > after
|
||||
}
|
||||
|
||||
func nextMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
|
||||
date := NextMercuryRetrogradeStrict(jde)
|
||||
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
|
||||
date = NextMercuryRetrogradeStrict(eventUTNextQueryTT(date))
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func lastMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
|
||||
date := LastMercuryRetrogradeStrict(jde)
|
||||
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
|
||||
date = LastMercuryRetrogradeStrict(eventUTLastQueryTT(date))
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func NextMercuryRetrograde(jde float64) float64 {
|
||||
date := mercuryRetrograde(jde)
|
||||
if date < jde {
|
||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
||||
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||
return mercuryRetrograde(nextConjunction + 2)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastMercuryRetrograde(jde float64) float64 {
|
||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
||||
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||
date := mercuryRetrograde(lastConjunction + 2)
|
||||
if date > jde {
|
||||
previousConjunction := mercuryConjunctionLegacy(lastConjunction-2, 0)
|
||||
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||
previousConjunction := LastMercuryConjunctionStrict(eventUTLastQueryTT(lastConjunction))
|
||||
return mercuryRetrograde(previousConjunction + 2)
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastMercuryRetrogradeStrict(jde float64) float64 {
|
||||
return LastMercuryRetrograde(jde)
|
||||
}
|
||||
|
||||
func NextMercuryRetrogradeStrict(jde float64) float64 {
|
||||
return NextMercuryRetrograde(jde)
|
||||
}
|
||||
|
||||
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
||||
date := NextMercuryRetrograde(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return NextMercuryRetrograde(date + MERCURY_S_PERIOD/2)
|
||||
}
|
||||
return date
|
||||
return nextMercuryTypedStation(jde, true)
|
||||
}
|
||||
|
||||
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||
date := NextMercuryRetrograde(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return NextMercuryRetrograde(date + 12)
|
||||
}
|
||||
return date
|
||||
return nextMercuryTypedStation(jde, false)
|
||||
}
|
||||
|
||||
func LastMercuryProgradeToRetrograde(jde float64) float64 {
|
||||
date := LastMercuryRetrograde(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return LastMercuryRetrograde(date - 12)
|
||||
}
|
||||
return date
|
||||
return lastMercuryTypedStation(jde, true)
|
||||
}
|
||||
|
||||
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||
date := LastMercuryRetrograde(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return LastMercuryRetrograde(date - MERCURY_S_PERIOD/2)
|
||||
}
|
||||
return date
|
||||
return lastMercuryTypedStation(jde, false)
|
||||
}
|
||||
|
||||
func MercurySunElongation(jde float64) float64 {
|
||||
lo1, bo1 := MercuryApparentLoBo(jde)
|
||||
lo2 := SunApparentLo(jde)
|
||||
lo2 := HSunApparentLo(jde)
|
||||
bo2 := HSunTrueBo(jde)
|
||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||
}
|
||||
|
||||
func mercurySunElongationN(jde float64, n int) float64 {
|
||||
lo1, bo1 := MercuryApparentLoBoN(jde, n)
|
||||
lo2 := HSunApparentLoN(jde, n)
|
||||
bo2 := HSunTrueBoN(jde, n)
|
||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||
}
|
||||
|
||||
func mercuryTrueElongationN(jde float64, n int) float64 {
|
||||
earth := mercuryHelioN(-1, jde, n)
|
||||
planetPos := mercuryHelioN(1, jde, n)
|
||||
geo := mercuryGeocentric(planetPos, earth)
|
||||
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
|
||||
}
|
||||
|
||||
func mercuryGreatestElongationInWindow(start, end float64) float64 {
|
||||
best := maximizeInWindow(start, end, 2.0, func(jd float64) float64 {
|
||||
return mercuryTrueElongationN(jd, mercuryEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return mercuryTrueElongationN(jd, -1)
|
||||
})
|
||||
return TD2UT(best, false)
|
||||
}
|
||||
|
||||
func mercuryEastElongationWindowEndingAt(inferior float64) (float64, float64) {
|
||||
lastSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(inferior))
|
||||
return lastSuperior + innerEventEpsilon, inferior - innerEventEpsilon
|
||||
}
|
||||
|
||||
func mercuryWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||
lastInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(superior))
|
||||
return lastInferior + innerEventEpsilon, superior - innerEventEpsilon
|
||||
}
|
||||
|
||||
func mercuryEastElongationWindowContaining(jde float64) (float64, float64) {
|
||||
nextInferior := NextMercuryInferiorConjunction(jde)
|
||||
start, end := mercuryEastElongationWindowEndingAt(nextInferior)
|
||||
if eventUTQueryBeforeOrEqual(start, jde) {
|
||||
return start, end
|
||||
}
|
||||
currentInferior := LastMercuryInferiorConjunction(jde)
|
||||
return mercuryEastElongationWindowEndingAt(currentInferior)
|
||||
}
|
||||
|
||||
func mercuryWestElongationWindowContaining(jde float64) (float64, float64) {
|
||||
nextSuperior := NextMercurySuperiorConjunction(jde)
|
||||
start, end := mercuryWestElongationWindowEndingAt(nextSuperior)
|
||||
if eventUTQueryBeforeOrEqual(start, jde) {
|
||||
return start, end
|
||||
}
|
||||
currentSuperior := LastMercurySuperiorConjunction(jde)
|
||||
return mercuryWestElongationWindowEndingAt(currentSuperior)
|
||||
}
|
||||
|
||||
func nextMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||
if east {
|
||||
start, windowEnd := mercuryEastElongationWindowContaining(jde)
|
||||
for {
|
||||
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||
start, windowEnd = mercuryEastElongationWindowEndingAt(nextInferior)
|
||||
}
|
||||
}
|
||||
start, windowEnd := mercuryWestElongationWindowContaining(jde)
|
||||
for {
|
||||
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
nextSuperior := NextMercurySuperiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||
start, windowEnd = mercuryWestElongationWindowEndingAt(nextSuperior)
|
||||
}
|
||||
}
|
||||
|
||||
func lastMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||
if east {
|
||||
start, windowEnd := mercuryEastElongationWindowContaining(jde)
|
||||
for {
|
||||
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
prevInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(start))
|
||||
start, windowEnd = mercuryEastElongationWindowEndingAt(prevInferior)
|
||||
}
|
||||
}
|
||||
start, windowEnd := mercuryWestElongationWindowContaining(jde)
|
||||
for {
|
||||
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
prevSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(start))
|
||||
start, windowEnd = mercuryWestElongationWindowEndingAt(prevSuperior)
|
||||
}
|
||||
}
|
||||
|
||||
func mercuryGreatestElongation(jde float64) float64 {
|
||||
solarRADelta := func(jde float64) float64 {
|
||||
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
||||
@@ -383,8 +512,8 @@ func mercuryGreatestElongation(jde float64) float64 {
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
||||
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||
currentRADelta := solarRADelta(jde)
|
||||
if currentRADelta > 0 {
|
||||
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0)
|
||||
@@ -417,56 +546,105 @@ func mercuryGreatestElongation(jde float64) float64 {
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongation(jde float64) float64 {
|
||||
date := mercuryGreatestElongation(jde)
|
||||
if date < jde {
|
||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
||||
return mercuryGreatestElongation(nextConjunction + 2)
|
||||
east := NextMercuryGreatestElongationEast(jde)
|
||||
west := NextMercuryGreatestElongationWest(jde)
|
||||
if sameEventJD(east, west) {
|
||||
return east
|
||||
}
|
||||
return date
|
||||
if east < west {
|
||||
return east
|
||||
}
|
||||
return west
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongation(jde float64) float64 {
|
||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
||||
date := mercuryGreatestElongation(lastConjunction + 2)
|
||||
if date > jde {
|
||||
previousConjunction := mercuryConjunctionLegacy(lastConjunction-2, 0)
|
||||
return mercuryGreatestElongation(previousConjunction + 2)
|
||||
east := LastMercuryGreatestElongationEast(jde)
|
||||
west := LastMercuryGreatestElongationWest(jde)
|
||||
if sameEventJD(east, west) {
|
||||
return east
|
||||
}
|
||||
return date
|
||||
if east > west {
|
||||
return east
|
||||
}
|
||||
return west
|
||||
}
|
||||
|
||||
func LastMercuryInferiorConjunctionInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryInferiorConjunction, NextMercuryInferiorConjunction)
|
||||
}
|
||||
|
||||
func NextMercuryInferiorConjunctionInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryInferiorConjunction, NextMercuryInferiorConjunction)
|
||||
}
|
||||
|
||||
func LastMercurySuperiorConjunctionInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercurySuperiorConjunction, NextMercurySuperiorConjunction)
|
||||
}
|
||||
|
||||
func NextMercurySuperiorConjunctionInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercurySuperiorConjunction, NextMercurySuperiorConjunction)
|
||||
}
|
||||
|
||||
func LastMercuryRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryRetrograde, NextMercuryRetrograde)
|
||||
}
|
||||
|
||||
func NextMercuryRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryRetrograde, NextMercuryRetrograde)
|
||||
}
|
||||
|
||||
func LastMercuryProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryProgradeToRetrograde, NextMercuryProgradeToRetrograde)
|
||||
}
|
||||
|
||||
func NextMercuryProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryProgradeToRetrograde, NextMercuryProgradeToRetrograde)
|
||||
}
|
||||
|
||||
func LastMercuryRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryRetrogradeToPrograde, NextMercuryRetrogradeToPrograde)
|
||||
}
|
||||
|
||||
func NextMercuryRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryRetrogradeToPrograde, NextMercuryRetrogradeToPrograde)
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongationInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongation, NextMercuryGreatestElongation)
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongationInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongation, NextMercuryGreatestElongation)
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongationEastInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongationEast, NextMercuryGreatestElongationEast)
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongationEastInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongationEast, NextMercuryGreatestElongationEast)
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongationWestInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongationWest, NextMercuryGreatestElongationWest)
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongationWestInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongationWest, NextMercuryGreatestElongationWest)
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongationEast(jde float64) float64 {
|
||||
date := NextMercuryGreatestElongation(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return NextMercuryGreatestElongation(date + 1)
|
||||
}
|
||||
return date
|
||||
return nextMercuryGreatestElongationTyped(jde, true)
|
||||
}
|
||||
|
||||
func NextMercuryGreatestElongationWest(jde float64) float64 {
|
||||
date := NextMercuryGreatestElongation(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return NextMercuryGreatestElongation(date + 1)
|
||||
}
|
||||
return date
|
||||
return nextMercuryGreatestElongationTyped(jde, false)
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongationEast(jde float64) float64 {
|
||||
date := LastMercuryGreatestElongation(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return LastMercuryGreatestElongation(date - 1)
|
||||
}
|
||||
return date
|
||||
return lastMercuryGreatestElongationTyped(jde, true)
|
||||
}
|
||||
|
||||
func LastMercuryGreatestElongationWest(jde float64) float64 {
|
||||
date := LastMercuryGreatestElongation(jde)
|
||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return LastMercuryGreatestElongation(date - 1)
|
||||
}
|
||||
return date
|
||||
return lastMercuryGreatestElongationTyped(jde, false)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func mercuryTTJDJST(year int, month time.Month, day, hour, minute, second int) float64 {
|
||||
loc := time.FixedZone("JST", 9*3600)
|
||||
return TD2UT(Date2JDE(time.Date(year, month, day, hour, minute, second, 0, loc).UTC()), true)
|
||||
}
|
||||
|
||||
func TestMercuryTypedStationRegression1929(t *testing.T) {
|
||||
loc := time.FixedZone("JST", 9*3600)
|
||||
const tolerance = 30.0 / 86400.0
|
||||
|
||||
query := mercuryTTJDJST(1929, time.September, 20, 0, 0, 0)
|
||||
wantP2R := mercuryTTJDJST(1929, time.September, 26, 1, 58, 0)
|
||||
wantR2P := mercuryTTJDJST(1929, time.October, 16, 23, 32, 33)
|
||||
|
||||
nextP2R := NextMercuryProgradeToRetrograde(query)
|
||||
nextR2P := NextMercuryRetrogradeToPrograde(query)
|
||||
if math.Abs(nextP2R-wantP2R) > tolerance {
|
||||
t.Fatalf("next P2R mismatch: got %s want %s", JDE2DateByZone(nextP2R, loc, false), JDE2DateByZone(wantP2R, loc, false))
|
||||
}
|
||||
if math.Abs(nextR2P-wantR2P) > tolerance {
|
||||
t.Fatalf("next R2P mismatch: got %s want %s", JDE2DateByZone(nextR2P, loc, false), JDE2DateByZone(wantR2P, loc, false))
|
||||
}
|
||||
|
||||
query = mercuryTTJDJST(1929, time.October, 20, 0, 0, 0)
|
||||
lastP2R := LastMercuryProgradeToRetrograde(query)
|
||||
lastR2P := LastMercuryRetrogradeToPrograde(query)
|
||||
if math.Abs(lastP2R-wantP2R) > tolerance {
|
||||
t.Fatalf("last P2R mismatch: got %s want %s", JDE2DateByZone(lastP2R, loc, false), JDE2DateByZone(wantP2R, loc, false))
|
||||
}
|
||||
if math.Abs(lastR2P-wantR2P) > tolerance {
|
||||
t.Fatalf("last R2P mismatch: got %s want %s", JDE2DateByZone(lastR2P, loc, false), JDE2DateByZone(wantR2P, loc, false))
|
||||
}
|
||||
}
|
||||
+7
-38
@@ -87,53 +87,22 @@ func NeptuneApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthNeptuneAway(jd float64) float64 {
|
||||
x, y, z := ANeptuneXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(7, jd, -1)
|
||||
}
|
||||
|
||||
func NeptuneApparentLo(jd float64) float64 {
|
||||
x, y, z := ANeptuneXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ANeptuneXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func NeptuneApparentBo(jd float64) float64 {
|
||||
x, y, z := ANeptuneXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ANeptuneXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func NeptuneApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := ANeptuneXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ANeptuneXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func NeptuneMag(jd float64) float64 {
|
||||
|
||||
+67
-66
@@ -9,7 +9,7 @@ import (
|
||||
// Pos
|
||||
|
||||
const (
|
||||
NEPTUNE_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 4332.59))
|
||||
NEPTUNE_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 60190.03))
|
||||
neptuneEventSearchN = 16
|
||||
neptunePhaseCoarseTolerance = 30.0 / 86400.0
|
||||
)
|
||||
@@ -40,6 +40,28 @@ func neptuneSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64
|
||||
return sub
|
||||
}
|
||||
|
||||
func neptuneRADerivative(jde, delta float64) float64 {
|
||||
sub := NeptuneApparentRa(jde+delta) - NeptuneApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func neptuneRADerivativeN(jde, delta float64, n int) float64 {
|
||||
sub := NeptuneApparentRaN(jde+delta, n) - NeptuneApparentRaN(jde-delta, n)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func neptuneConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||
//0=last 1=next
|
||||
daysPerDegree := NEPTUNE_S_PERIOD / 360
|
||||
@@ -94,113 +116,92 @@ func neptuneConjunction(jde, degree float64, next uint8) float64 {
|
||||
}
|
||||
|
||||
func LastNeptuneConjunction(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 0, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 0, neptuneConjunction)
|
||||
}
|
||||
|
||||
func NextNeptuneConjunction(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 0, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 0, neptuneConjunction)
|
||||
}
|
||||
|
||||
func LastNeptuneOpposition(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 180, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 180, neptuneConjunction)
|
||||
}
|
||||
|
||||
func NextNeptuneOpposition(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 180, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 180, neptuneConjunction)
|
||||
}
|
||||
|
||||
func NextNeptuneEasternQuadrature(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 90, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 90, neptuneConjunction)
|
||||
}
|
||||
|
||||
func LastNeptuneEasternQuadrature(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 90, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 90, neptuneConjunction)
|
||||
}
|
||||
|
||||
func NextNeptuneWesternQuadrature(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 270, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 270, neptuneConjunction)
|
||||
}
|
||||
|
||||
func LastNeptuneWesternQuadrature(jde float64) float64 {
|
||||
return neptuneConjunction(jde, 270, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 270, neptuneConjunction)
|
||||
}
|
||||
|
||||
func neptuneRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
||||
//0=last 1=next
|
||||
raRate := func(jde float64, delta float64) float64 {
|
||||
sub := NeptuneApparentRa(jde+delta) - NeptuneApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
jde = neptuneConjunctionFull(jde, 180, 1)
|
||||
func neptuneRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||
oppositionTT := TD2UT(oppositionJD, true)
|
||||
startTT := oppositionTT
|
||||
endTT := oppositionTT
|
||||
if searchBeforeOpposition {
|
||||
jde -= 60
|
||||
easternQuadratureUT := neptuneConjunction(oppositionTT, 90, 0)
|
||||
startTT = TD2UT(easternQuadratureUT, true)
|
||||
} else {
|
||||
jde += 60
|
||||
westernQuadratureUT := neptuneConjunction(oppositionTT, 270, 1)
|
||||
endTT = TD2UT(westernQuadratureUT, true)
|
||||
}
|
||||
for {
|
||||
currentRate := raRate(jde, 1.0/86400.0)
|
||||
if math.Abs(currentRate) > 0.55 {
|
||||
jde += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
estimateJD := jde
|
||||
for {
|
||||
prevJD := estimateJD
|
||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
estimateJD = prevJD - rateValue/rateSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||
return raRate(jd, 0.5/86400.0)
|
||||
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||
return neptuneRADerivativeN(jd, 1.0/86400.0, neptuneEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return neptuneRADerivative(jd, 0.5/86400.0)
|
||||
})
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func NextNeptuneRetrogradeToPrograde(jde float64) float64 {
|
||||
date := neptuneRetrograde(jde, false)
|
||||
if date < jde {
|
||||
oppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||
return neptuneRetrograde(oppositionJD+10, false)
|
||||
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||
date := neptuneRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||
return neptuneRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||
}
|
||||
|
||||
func LastNeptuneRetrogradeToPrograde(jde float64) float64 {
|
||||
jde = neptuneConjunctionFull(jde, 180, 0) - 10
|
||||
date := neptuneRetrograde(jde, false)
|
||||
if date > jde {
|
||||
oppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||
return neptuneRetrograde(oppositionJD-10, false)
|
||||
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||
date := neptuneRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
previousOppositionJD := neptuneConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||
return neptuneRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||
}
|
||||
|
||||
func NextNeptuneProgradeToRetrograde(jde float64) float64 {
|
||||
date := neptuneRetrograde(jde, true)
|
||||
if date < jde {
|
||||
oppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||
return neptuneRetrograde(oppositionJD+10, true)
|
||||
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||
date := neptuneRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
followingOppositionJD := neptuneConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||
return neptuneRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||
}
|
||||
|
||||
func LastNeptuneProgradeToRetrograde(jde float64) float64 {
|
||||
jde = neptuneConjunctionFull(jde, 180, 0) - 10
|
||||
date := neptuneRetrograde(jde, true)
|
||||
if date > jde {
|
||||
oppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||
return neptuneRetrograde(oppositionJD-10, true)
|
||||
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||
date := neptuneRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||
return neptuneRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestOuterPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
seed float64
|
||||
lastFn func(float64) float64
|
||||
nextFn func(float64) float64
|
||||
}{
|
||||
{name: "JupiterConjunction", seed: NextJupiterConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterConjunction, nextFn: NextJupiterConjunction},
|
||||
{name: "JupiterOpposition", seed: NextJupiterOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterOpposition, nextFn: NextJupiterOpposition},
|
||||
{name: "JupiterEasternQuadrature", seed: NextJupiterEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterEasternQuadrature, nextFn: NextJupiterEasternQuadrature},
|
||||
{name: "JupiterWesternQuadrature", seed: NextJupiterWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterWesternQuadrature, nextFn: NextJupiterWesternQuadrature},
|
||||
{name: "JupiterP2R", seed: NextJupiterProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterProgradeToRetrograde, nextFn: NextJupiterProgradeToRetrograde},
|
||||
{name: "JupiterR2P", seed: NextJupiterRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterRetrogradeToPrograde, nextFn: NextJupiterRetrogradeToPrograde},
|
||||
{name: "SaturnOpposition", seed: NextSaturnOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnOpposition, nextFn: NextSaturnOpposition},
|
||||
{name: "SaturnP2R", seed: NextSaturnProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnProgradeToRetrograde, nextFn: NextSaturnProgradeToRetrograde},
|
||||
{name: "SaturnR2P", seed: NextSaturnRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnRetrogradeToPrograde, nextFn: NextSaturnRetrogradeToPrograde},
|
||||
{name: "UranusOpposition", seed: NextUranusOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusOpposition, nextFn: NextUranusOpposition},
|
||||
{name: "UranusP2R", seed: NextUranusProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusProgradeToRetrograde, nextFn: NextUranusProgradeToRetrograde},
|
||||
{name: "UranusR2P", seed: NextUranusRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusRetrogradeToPrograde, nextFn: NextUranusRetrogradeToPrograde},
|
||||
{name: "NeptuneOpposition", seed: NextNeptuneOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneOpposition, nextFn: NextNeptuneOpposition},
|
||||
{name: "NeptuneP2R", seed: NextNeptuneProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneProgradeToRetrograde, nextFn: NextNeptuneProgradeToRetrograde},
|
||||
{name: "NeptuneR2P", seed: NextNeptuneRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneRetrogradeToPrograde, nextFn: NextNeptuneRetrogradeToPrograde},
|
||||
{name: "MarsConjunction", seed: NextMarsConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsConjunction, nextFn: NextMarsConjunction},
|
||||
{name: "MarsOpposition", seed: NextMarsOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsOpposition, nextFn: NextMarsOpposition},
|
||||
{name: "MarsEasternQuadrature", seed: NextMarsEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsEasternQuadrature, nextFn: NextMarsEasternQuadrature},
|
||||
{name: "MarsWesternQuadrature", seed: NextMarsWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsWesternQuadrature, nextFn: NextMarsWesternQuadrature},
|
||||
{name: "MarsP2R", seed: NextMarsProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsProgradeToRetrograde, nextFn: NextMarsProgradeToRetrograde},
|
||||
{name: "MarsR2P", seed: NextMarsRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsRetrogradeToPrograde, nextFn: NextMarsRetrogradeToPrograde},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
queryTT := TD2UT(tc.seed, true)
|
||||
last := tc.lastFn(queryTT)
|
||||
next := tc.nextFn(queryTT)
|
||||
if !sameEventJD(last, tc.seed) {
|
||||
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||
}
|
||||
if !sameEventJD(next, tc.seed) {
|
||||
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ttjdUTC(year, month, day, hour, min, sec int) float64 {
|
||||
return TD2UT(Date2JDE(time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC)), true)
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type outerTruthBaselineFile struct {
|
||||
Events []outerTruthBaselineEvent `json:"events"`
|
||||
}
|
||||
|
||||
type outerTruthBaselineEvent struct {
|
||||
Planet string `json:"planet"`
|
||||
Kind string `json:"kind"`
|
||||
HintKind string `json:"hint_kind"`
|
||||
NAOJHintJST string `json:"naoj_hint_jst"`
|
||||
Precision string `json:"precision"`
|
||||
CandidateJST string `json:"candidate_jst"`
|
||||
VerifiedJST string `json:"verified_jst"`
|
||||
CandidateSource string `json:"candidate_source"`
|
||||
}
|
||||
|
||||
func loadOuterTruthBaseline(t *testing.T) outerTruthBaselineFile {
|
||||
t.Helper()
|
||||
|
||||
paths := [][]string{
|
||||
{
|
||||
"testdata/jpl_outer_event_baseline.json",
|
||||
"basic/testdata/jpl_outer_event_baseline.json",
|
||||
},
|
||||
{
|
||||
"testdata/jpl_outer_event_baseline_21c_sample.json",
|
||||
"basic/testdata/jpl_outer_event_baseline_21c_sample.json",
|
||||
},
|
||||
}
|
||||
|
||||
var merged outerTruthBaselineFile
|
||||
for index, candidates := range paths {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
for _, path := range candidates {
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
var baseline outerTruthBaselineFile
|
||||
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
merged.Events = append(merged.Events, baseline.Events...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil && index == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(merged.Events) == 0 {
|
||||
t.Fatal("empty outer truth baseline file")
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func outerTruthTolerance(event outerTruthBaselineEvent) time.Duration {
|
||||
switch event.Kind {
|
||||
case "CONJ", "OPP", "EQE", "EQW":
|
||||
return 2 * time.Minute
|
||||
default:
|
||||
return 2 * time.Minute
|
||||
}
|
||||
}
|
||||
|
||||
func outerTruthEventFuncs(t *testing.T, event outerTruthBaselineEvent) (func(float64) float64, func(float64) float64) {
|
||||
t.Helper()
|
||||
switch event.Planet + ":" + event.Kind {
|
||||
case "Jupiter:CONJ":
|
||||
return LastJupiterConjunction, NextJupiterConjunction
|
||||
case "Jupiter:OPP":
|
||||
return LastJupiterOpposition, NextJupiterOpposition
|
||||
case "Jupiter:EQE":
|
||||
return LastJupiterEasternQuadrature, NextJupiterEasternQuadrature
|
||||
case "Jupiter:EQW":
|
||||
return LastJupiterWesternQuadrature, NextJupiterWesternQuadrature
|
||||
case "Jupiter:P2R":
|
||||
return LastJupiterProgradeToRetrograde, NextJupiterProgradeToRetrograde
|
||||
case "Jupiter:R2P":
|
||||
return LastJupiterRetrogradeToPrograde, NextJupiterRetrogradeToPrograde
|
||||
case "Saturn:CONJ":
|
||||
return LastSaturnConjunction, NextSaturnConjunction
|
||||
case "Saturn:OPP":
|
||||
return LastSaturnOpposition, NextSaturnOpposition
|
||||
case "Saturn:EQE":
|
||||
return LastSaturnEasternQuadrature, NextSaturnEasternQuadrature
|
||||
case "Saturn:EQW":
|
||||
return LastSaturnWesternQuadrature, NextSaturnWesternQuadrature
|
||||
case "Saturn:P2R":
|
||||
return LastSaturnProgradeToRetrograde, NextSaturnProgradeToRetrograde
|
||||
case "Saturn:R2P":
|
||||
return LastSaturnRetrogradeToPrograde, NextSaturnRetrogradeToPrograde
|
||||
case "Uranus:CONJ":
|
||||
return LastUranusConjunction, NextUranusConjunction
|
||||
case "Uranus:OPP":
|
||||
return LastUranusOpposition, NextUranusOpposition
|
||||
case "Uranus:EQE":
|
||||
return LastUranusEasternQuadrature, NextUranusEasternQuadrature
|
||||
case "Uranus:EQW":
|
||||
return LastUranusWesternQuadrature, NextUranusWesternQuadrature
|
||||
case "Uranus:P2R":
|
||||
return LastUranusProgradeToRetrograde, NextUranusProgradeToRetrograde
|
||||
case "Uranus:R2P":
|
||||
return LastUranusRetrogradeToPrograde, NextUranusRetrogradeToPrograde
|
||||
case "Neptune:CONJ":
|
||||
return LastNeptuneConjunction, NextNeptuneConjunction
|
||||
case "Neptune:OPP":
|
||||
return LastNeptuneOpposition, NextNeptuneOpposition
|
||||
case "Neptune:EQE":
|
||||
return LastNeptuneEasternQuadrature, NextNeptuneEasternQuadrature
|
||||
case "Neptune:EQW":
|
||||
return LastNeptuneWesternQuadrature, NextNeptuneWesternQuadrature
|
||||
case "Neptune:P2R":
|
||||
return LastNeptuneProgradeToRetrograde, NextNeptuneProgradeToRetrograde
|
||||
case "Neptune:R2P":
|
||||
return LastNeptuneRetrogradeToPrograde, NextNeptuneRetrogradeToPrograde
|
||||
default:
|
||||
t.Fatalf("unsupported outer event %s:%s", event.Planet, event.Kind)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func assertOuterTruthBaselineEvent(t *testing.T, event outerTruthBaselineEvent, lastFn, nextFn func(float64) float64) {
|
||||
t.Helper()
|
||||
when := parseInnerBaselineTime(t, event.VerifiedJST)
|
||||
before := when.Add(-7 * 24 * time.Hour)
|
||||
after := when.Add(7 * 24 * time.Hour)
|
||||
next := JDE2DateByZone(nextFn(toUTJD(before)), when.Location(), false)
|
||||
last := JDE2DateByZone(lastFn(toUTJD(after)), when.Location(), false)
|
||||
tolerance := outerTruthTolerance(event)
|
||||
|
||||
if diff := next.Sub(when); diff < -tolerance || diff > tolerance {
|
||||
t.Fatalf("%s %s next mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, next, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||
}
|
||||
if diff := last.Sub(when); diff < -tolerance || diff > tolerance {
|
||||
t.Fatalf("%s %s last mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, last, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOuterPlanetPhaseTruthAgainstJPL(t *testing.T) {
|
||||
baseline := loadOuterTruthBaseline(t)
|
||||
for _, event := range baseline.Events {
|
||||
event := event
|
||||
switch event.Kind {
|
||||
case "P2R", "R2P":
|
||||
// Station rows are retained as JPL apparent-RA reference data for
|
||||
// future refinement. Current station behavior is constrained by the
|
||||
// library's existing station baseline instead of these reference rows.
|
||||
continue
|
||||
}
|
||||
name := strings.Join([]string{event.Planet, event.Kind, event.VerifiedJST}, "_")
|
||||
t.Run(name, func(t *testing.T) {
|
||||
lastFn, nextFn := outerTruthEventFuncs(t, event)
|
||||
assertOuterTruthBaselineEvent(t, event, lastFn, nextFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOuterPlanetStationJPLReferenceLoaded(t *testing.T) {
|
||||
baseline := loadOuterTruthBaseline(t)
|
||||
count := 0
|
||||
for _, event := range baseline.Events {
|
||||
switch event.Kind {
|
||||
case "P2R", "R2P":
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
t.Fatal("missing outer station JPL reference rows")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"b612.me/astro/planet"
|
||||
. "b612.me/astro/tools"
|
||||
)
|
||||
|
||||
type planetGeocentricPosition struct {
|
||||
x float64
|
||||
y float64
|
||||
z float64
|
||||
lo float64
|
||||
bo float64
|
||||
}
|
||||
|
||||
func planetHeliocentricXYZN(planetIndex int, jd float64, n int) (float64, float64, float64) {
|
||||
l := planet.WherePlanetN(planetIndex, 0, jd, n)
|
||||
b := planet.WherePlanetN(planetIndex, 1, jd, n)
|
||||
r := planet.WherePlanetN(planetIndex, 2, jd, n)
|
||||
return sphericalToRectangular(l, b, r)
|
||||
}
|
||||
|
||||
func earthHeliocentricXYZN(jd float64, n int) (float64, float64, float64) {
|
||||
l := planet.WherePlanetN(-1, 0, jd, n)
|
||||
b := planet.WherePlanetN(-1, 1, jd, n)
|
||||
r := planet.WherePlanetN(-1, 2, jd, n)
|
||||
return sphericalToRectangular(l, b, r)
|
||||
}
|
||||
|
||||
func sphericalToRectangular(lo, bo, radius float64) (float64, float64, float64) {
|
||||
cosBo := math.Cos(bo * math.Pi / 180)
|
||||
return radius * cosBo * math.Cos(lo*math.Pi/180),
|
||||
radius * cosBo * math.Sin(lo*math.Pi/180),
|
||||
radius * math.Sin(bo*math.Pi/180)
|
||||
}
|
||||
|
||||
func geocentricPositionFromRectangular(x, y, z float64) planetGeocentricPosition {
|
||||
lo := math.Atan2(y, x) * 180 / math.Pi
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y)) * 180 / math.Pi
|
||||
return planetGeocentricPosition{
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
lo: Limit360(lo),
|
||||
bo: bo,
|
||||
}
|
||||
}
|
||||
|
||||
func planetGeocentricPositionN(planetIndex int, planetJD, earthJD float64, n int) planetGeocentricPosition {
|
||||
px, py, pz := planetHeliocentricXYZN(planetIndex, planetJD, n)
|
||||
ex, ey, ez := earthHeliocentricXYZN(earthJD, n)
|
||||
return geocentricPositionFromRectangular(px-ex, py-ey, pz-ez)
|
||||
}
|
||||
|
||||
func planetGeocentricPositionWithEarthN(planetIndex int, planetJD float64, ex, ey, ez float64, n int) planetGeocentricPosition {
|
||||
px, py, pz := planetHeliocentricXYZN(planetIndex, planetJD, n)
|
||||
return geocentricPositionFromRectangular(px-ex, py-ey, pz-ez)
|
||||
}
|
||||
|
||||
func planetApparentGeocentricPositionN(planetIndex int, jd float64, n int) (planetGeocentricPosition, float64) {
|
||||
ex, ey, ez := earthHeliocentricXYZN(jd, n)
|
||||
geoNow := planetGeocentricPositionWithEarthN(planetIndex, jd, ex, ey, ez, n)
|
||||
tau := 0.0057755183 * math.Sqrt(geoNow.x*geoNow.x+geoNow.y*geoNow.y+geoNow.z*geoNow.z)
|
||||
geo := planetGeocentricPositionWithEarthN(planetIndex, jd-tau, ex, ey, ez, n)
|
||||
baseLo := geo.lo
|
||||
baseBo := geo.bo
|
||||
geo.lo = Limit360(baseLo + GXCLo(baseLo, baseBo, jd)/3600.0 + Nutation2000Bi(jd))
|
||||
geo.bo = baseBo + GXCBo(baseLo, baseBo, jd)/3600.0
|
||||
return geo, tau
|
||||
}
|
||||
|
||||
func planetTrueGeocentricPositionN(planetIndex int, jd float64, n int) (planetGeocentricPosition, float64) {
|
||||
ex, ey, ez := earthHeliocentricXYZN(jd, n)
|
||||
geoNow := planetGeocentricPositionWithEarthN(planetIndex, jd, ex, ey, ez, n)
|
||||
tau := 0.0057755183 * math.Sqrt(geoNow.x*geoNow.x+geoNow.y*geoNow.y+geoNow.z*geoNow.z)
|
||||
return planetGeocentricPositionWithEarthN(planetIndex, jd-tau, ex, ey, ez, n), tau
|
||||
}
|
||||
|
||||
func planetEarthAwayExplicitN(planetIndex int, jd float64, n int) float64 {
|
||||
geoNow := planetGeocentricPositionN(planetIndex, jd, jd, n)
|
||||
return math.Sqrt(geoNow.x*geoNow.x + geoNow.y*geoNow.y + geoNow.z*geoNow.z)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type planetApparentSample struct {
|
||||
Body string `json:"body"`
|
||||
InputUTC string `json:"input_utc"`
|
||||
RightAscension float64 `json:"right_ascension"`
|
||||
Declination float64 `json:"declination"`
|
||||
EclipticLongitude float64 `json:"ecliptic_longitude"`
|
||||
EclipticLatitude float64 `json:"ecliptic_latitude"`
|
||||
}
|
||||
|
||||
func TestPlanetApparentCoordinatesMatchHorizonsBaseline(t *testing.T) {
|
||||
data, err := os.ReadFile("testdata/planet_apparent_baseline.json")
|
||||
if err != nil {
|
||||
t.Fatalf("read baseline: %v", err)
|
||||
}
|
||||
|
||||
var samples []planetApparentSample
|
||||
if err := json.Unmarshal(data, &samples); err != nil {
|
||||
t.Fatalf("decode baseline: %v", err)
|
||||
}
|
||||
|
||||
type apparentCase struct {
|
||||
lo func(float64) float64
|
||||
bo func(float64) float64
|
||||
ra func(float64) float64
|
||||
dec func(float64) float64
|
||||
}
|
||||
|
||||
cases := map[string]apparentCase{
|
||||
"mercury": {lo: MercuryApparentLo, bo: MercuryApparentBo, ra: MercuryApparentRa, dec: MercuryApparentDec},
|
||||
"venus": {lo: VenusApparentLo, bo: VenusApparentBo, ra: VenusApparentRa, dec: VenusApparentDec},
|
||||
"mars": {lo: MarsApparentLo, bo: MarsApparentBo, ra: MarsApparentRa, dec: MarsApparentDec},
|
||||
"jupiter": {lo: JupiterApparentLo, bo: JupiterApparentBo, ra: JupiterApparentRa, dec: JupiterApparentDec},
|
||||
"saturn": {lo: SaturnApparentLo, bo: SaturnApparentBo, ra: SaturnApparentRa, dec: SaturnApparentDec},
|
||||
"uranus": {lo: UranusApparentLo, bo: UranusApparentBo, ra: UranusApparentRa, dec: UranusApparentDec},
|
||||
"neptune": {lo: NeptuneApparentLo, bo: NeptuneApparentBo, ra: NeptuneApparentRa, dec: NeptuneApparentDec},
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(cases))
|
||||
for _, sample := range samples {
|
||||
tc, ok := cases[sample.Body]
|
||||
if !ok {
|
||||
t.Fatalf("unknown body %q", sample.Body)
|
||||
}
|
||||
if seen[sample.Body] {
|
||||
t.Fatalf("duplicate body %q in apparent baseline", sample.Body)
|
||||
}
|
||||
seen[sample.Body] = true
|
||||
|
||||
date, err := time.Parse(time.RFC3339, sample.InputUTC)
|
||||
if err != nil {
|
||||
t.Fatalf("parse sample time %q: %v", sample.InputUTC, err)
|
||||
}
|
||||
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||
prefix := sample.Body + "." + sample.InputUTC
|
||||
|
||||
assertPlanetApparentAngleClose(t, prefix+".RightAscension", tc.ra(jd), sample.RightAscension, 0.001)
|
||||
assertPlanetPhaseClose(t, prefix+".Declination", tc.dec(jd), sample.Declination, 0.001)
|
||||
assertPlanetApparentAngleClose(t, prefix+".EclipticLongitude", tc.lo(jd), sample.EclipticLongitude, 0.001)
|
||||
assertPlanetPhaseClose(t, prefix+".EclipticLatitude", tc.bo(jd), sample.EclipticLatitude, 0.001)
|
||||
}
|
||||
|
||||
for body := range cases {
|
||||
if !seen[body] {
|
||||
t.Fatalf("missing body %q in apparent baseline", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertPlanetApparentAngleClose(t *testing.T, name string, got, want, tolerance float64) {
|
||||
t.Helper()
|
||||
if diff := angleDiffAbs(got, want); diff > tolerance {
|
||||
t.Fatalf("%s mismatch: got %.12f want %.12f diff %.12f tolerance %.12f", name, got, want, diff, tolerance)
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,8 @@ func planetXYZN(planetIndex int, jd float64, n int) (float64, float64, float64)
|
||||
}
|
||||
|
||||
func planetApparentLoBoN(planetIndex int, jd float64, n int) (float64, float64) {
|
||||
x, y, z := planetXYZN(planetIndex, jd, n)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = planetXYZN(planetIndex, jd-to, n)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = Limit360(lo*180/math.Pi) + Nutation2000Bi(jd)
|
||||
bo = bo * 180 / math.Pi
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(planetIndex, jd, n)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func planetApparentRaManualN(planetIndex int, jd float64, n int) float64 {
|
||||
@@ -57,8 +51,7 @@ func planetApparentRaDecManualN(planetIndex int, jd float64, n int) (float64, fl
|
||||
}
|
||||
|
||||
func planetEarthAwayN(planetIndex int, jd float64, n int) float64 {
|
||||
x, y, z := planetXYZN(planetIndex, jd, n)
|
||||
return math.Sqrt(x*x + y*y + z*z)
|
||||
return planetEarthAwayExplicitN(planetIndex, jd, n)
|
||||
}
|
||||
|
||||
func planetHeightN(jde, lon, lat, timezone float64, n int, apparentRaDec func(float64, int) (float64, float64)) float64 {
|
||||
|
||||
+7
-38
@@ -87,53 +87,22 @@ func SaturnApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthSaturnAway(jd float64) float64 {
|
||||
x, y, z := ASaturnXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(5, jd, -1)
|
||||
}
|
||||
|
||||
func SaturnApparentLo(jd float64) float64 {
|
||||
x, y, z := ASaturnXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ASaturnXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(5, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func SaturnApparentBo(jd float64) float64 {
|
||||
x, y, z := ASaturnXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ASaturnXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(5, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func SaturnApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := ASaturnXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ASaturnXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(5, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func SaturnMag(jd float64) float64 {
|
||||
|
||||
+66
-65
@@ -40,6 +40,28 @@ func saturnSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
||||
return sub
|
||||
}
|
||||
|
||||
func saturnRADerivative(jde, delta float64) float64 {
|
||||
sub := SaturnApparentRa(jde+delta) - SaturnApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func saturnRADerivativeN(jde, delta float64, n int) float64 {
|
||||
sub := SaturnApparentRaN(jde+delta, n) - SaturnApparentRaN(jde-delta, n)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func saturnConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||
//0=last 1=next
|
||||
daysPerDegree := SATURN_S_PERIOD / 360
|
||||
@@ -94,113 +116,92 @@ func saturnConjunction(jde, degree float64, next uint8) float64 {
|
||||
}
|
||||
|
||||
func LastSaturnConjunction(jde float64) float64 {
|
||||
return saturnConjunction(jde, 0, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 0, saturnConjunction)
|
||||
}
|
||||
|
||||
func NextSaturnConjunction(jde float64) float64 {
|
||||
return saturnConjunction(jde, 0, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 0, saturnConjunction)
|
||||
}
|
||||
|
||||
func LastSaturnOpposition(jde float64) float64 {
|
||||
return saturnConjunction(jde, 180, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 180, saturnConjunction)
|
||||
}
|
||||
|
||||
func NextSaturnOpposition(jde float64) float64 {
|
||||
return saturnConjunction(jde, 180, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 180, saturnConjunction)
|
||||
}
|
||||
|
||||
func NextSaturnEasternQuadrature(jde float64) float64 {
|
||||
return saturnConjunction(jde, 90, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 90, saturnConjunction)
|
||||
}
|
||||
|
||||
func LastSaturnEasternQuadrature(jde float64) float64 {
|
||||
return saturnConjunction(jde, 90, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 90, saturnConjunction)
|
||||
}
|
||||
|
||||
func NextSaturnWesternQuadrature(jde float64) float64 {
|
||||
return saturnConjunction(jde, 270, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 270, saturnConjunction)
|
||||
}
|
||||
|
||||
func LastSaturnWesternQuadrature(jde float64) float64 {
|
||||
return saturnConjunction(jde, 270, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 270, saturnConjunction)
|
||||
}
|
||||
|
||||
func saturnRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
||||
//0=last 1=next
|
||||
raRate := func(jde float64, delta float64) float64 {
|
||||
sub := SaturnApparentRa(jde+delta) - SaturnApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
jde = saturnConjunctionFull(jde, 180, 1)
|
||||
func saturnRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||
oppositionTT := TD2UT(oppositionJD, true)
|
||||
startTT := oppositionTT
|
||||
endTT := oppositionTT
|
||||
if searchBeforeOpposition {
|
||||
jde -= 60
|
||||
easternQuadratureUT := saturnConjunction(oppositionTT, 90, 0)
|
||||
startTT = TD2UT(easternQuadratureUT, true)
|
||||
} else {
|
||||
jde += 60
|
||||
westernQuadratureUT := saturnConjunction(oppositionTT, 270, 1)
|
||||
endTT = TD2UT(westernQuadratureUT, true)
|
||||
}
|
||||
for {
|
||||
currentRate := raRate(jde, 1.0/86400.0)
|
||||
if math.Abs(currentRate) > 0.55 {
|
||||
jde += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
estimateJD := jde
|
||||
for {
|
||||
prevJD := estimateJD
|
||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
estimateJD = prevJD - rateValue/rateSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||
return raRate(jd, 0.5/86400.0)
|
||||
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||
return saturnRADerivativeN(jd, 1.0/86400.0, saturnEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return saturnRADerivative(jd, 0.5/86400.0)
|
||||
})
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func NextSaturnRetrogradeToPrograde(jde float64) float64 {
|
||||
date := saturnRetrograde(jde, false)
|
||||
if date < jde {
|
||||
oppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||
return saturnRetrograde(oppositionJD+10, false)
|
||||
lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||
date := saturnRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
nextOppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||
return saturnRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||
}
|
||||
|
||||
func LastSaturnRetrogradeToPrograde(jde float64) float64 {
|
||||
jde = saturnConjunctionFull(jde, 180, 0) - 10
|
||||
date := saturnRetrograde(jde, false)
|
||||
if date > jde {
|
||||
oppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||
return saturnRetrograde(oppositionJD-10, false)
|
||||
lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||
date := saturnRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
previousOppositionJD := saturnConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||
return saturnRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||
}
|
||||
|
||||
func NextSaturnProgradeToRetrograde(jde float64) float64 {
|
||||
date := saturnRetrograde(jde, true)
|
||||
if date < jde {
|
||||
oppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||
return saturnRetrograde(oppositionJD+10, true)
|
||||
nextOppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||
date := saturnRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
followingOppositionJD := saturnConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||
return saturnRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||
}
|
||||
|
||||
func LastSaturnProgradeToRetrograde(jde float64) float64 {
|
||||
jde = saturnConjunctionFull(jde, 180, 0) - 10
|
||||
date := saturnRetrograde(jde, true)
|
||||
if date > jde {
|
||||
oppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||
return saturnRetrograde(oppositionJD-10, true)
|
||||
nextOppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||
date := saturnRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||
return saturnRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type stationEvent struct {
|
||||
when time.Time
|
||||
kind string
|
||||
}
|
||||
|
||||
type stationTruthCase struct {
|
||||
name string
|
||||
events []stationEvent
|
||||
lastR2P func(float64) float64
|
||||
nextR2P func(float64) float64
|
||||
lastP2R func(float64) float64
|
||||
nextP2R func(float64) float64
|
||||
}
|
||||
|
||||
func mustJST(value string) time.Time {
|
||||
loc := time.FixedZone("JST", 9*3600)
|
||||
t, err := time.ParseInLocation("2006-01-02 15:04", value, loc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func toUTJD(t time.Time) float64 {
|
||||
return TD2UT(Date2JDE(t.UTC()), true)
|
||||
}
|
||||
|
||||
func TestStationTruthAgainstNAOJ(t *testing.T) {
|
||||
cases := []stationTruthCase{
|
||||
{
|
||||
name: "Mars",
|
||||
events: []stationEvent{
|
||||
{when: mustJST("2024-12-08 05:59"), kind: "P2R"},
|
||||
{when: mustJST("2025-01-16 11:39"), kind: "OPP"},
|
||||
{when: mustJST("2025-02-24 18:35"), kind: "R2P"},
|
||||
{when: mustJST("2027-01-12 01:10"), kind: "P2R"},
|
||||
{when: mustJST("2027-02-20 00:51"), kind: "OPP"},
|
||||
{when: mustJST("2027-04-03 02:33"), kind: "R2P"},
|
||||
},
|
||||
lastR2P: LastMarsRetrogradeToPrograde,
|
||||
nextR2P: NextMarsRetrogradeToPrograde,
|
||||
lastP2R: LastMarsProgradeToRetrograde,
|
||||
nextP2R: NextMarsProgradeToRetrograde,
|
||||
},
|
||||
{
|
||||
name: "Jupiter",
|
||||
events: []stationEvent{
|
||||
{when: mustJST("2024-10-09 16:13"), kind: "P2R"},
|
||||
{when: mustJST("2024-12-08 05:58"), kind: "OPP"},
|
||||
{when: mustJST("2025-02-04 22:07"), kind: "R2P"},
|
||||
{when: mustJST("2025-11-12 04:54"), kind: "P2R"},
|
||||
{when: mustJST("2026-01-10 17:42"), kind: "OPP"},
|
||||
{when: mustJST("2026-03-11 11:44"), kind: "R2P"},
|
||||
{when: mustJST("2026-12-13 21:03"), kind: "P2R"},
|
||||
{when: mustJST("2027-02-11 09:29"), kind: "OPP"},
|
||||
{when: mustJST("2027-04-13 15:17"), kind: "R2P"},
|
||||
},
|
||||
lastR2P: LastJupiterRetrogradeToPrograde,
|
||||
nextR2P: NextJupiterRetrogradeToPrograde,
|
||||
lastP2R: LastJupiterProgradeToRetrograde,
|
||||
nextP2R: NextJupiterProgradeToRetrograde,
|
||||
},
|
||||
{
|
||||
name: "Saturn",
|
||||
events: []stationEvent{
|
||||
{when: mustJST("2024-07-01 06:15"), kind: "P2R"},
|
||||
{when: mustJST("2024-09-08 13:35"), kind: "OPP"},
|
||||
{when: mustJST("2024-11-16 14:57"), kind: "R2P"},
|
||||
{when: mustJST("2025-07-14 16:57"), kind: "P2R"},
|
||||
{when: mustJST("2025-09-21 14:46"), kind: "OPP"},
|
||||
{when: mustJST("2025-11-29 09:35"), kind: "R2P"},
|
||||
{when: mustJST("2026-07-28 08:09"), kind: "P2R"},
|
||||
{when: mustJST("2026-10-04 21:29"), kind: "OPP"},
|
||||
{when: mustJST("2026-12-12 08:21"), kind: "R2P"},
|
||||
{when: mustJST("2027-08-11 02:53"), kind: "P2R"},
|
||||
{when: mustJST("2027-10-18 09:36"), kind: "OPP"},
|
||||
{when: mustJST("2027-12-25 12:05"), kind: "R2P"},
|
||||
},
|
||||
lastR2P: LastSaturnRetrogradeToPrograde,
|
||||
nextR2P: NextSaturnRetrogradeToPrograde,
|
||||
lastP2R: LastSaturnProgradeToRetrograde,
|
||||
nextP2R: NextSaturnProgradeToRetrograde,
|
||||
},
|
||||
{
|
||||
name: "Uranus",
|
||||
events: []stationEvent{
|
||||
{when: mustJST("2024-01-27 19:50"), kind: "R2P"},
|
||||
{when: mustJST("2024-09-02 00:44"), kind: "P2R"},
|
||||
{when: mustJST("2024-11-17 11:45"), kind: "OPP"},
|
||||
{when: mustJST("2025-01-31 04:04"), kind: "R2P"},
|
||||
{when: mustJST("2025-09-06 13:55"), kind: "P2R"},
|
||||
{when: mustJST("2025-11-21 21:25"), kind: "OPP"},
|
||||
{when: mustJST("2026-02-04 13:37"), kind: "R2P"},
|
||||
{when: mustJST("2026-09-11 03:19"), kind: "P2R"},
|
||||
{when: mustJST("2026-11-26 07:41"), kind: "OPP"},
|
||||
{when: mustJST("2027-02-08 23:03"), kind: "R2P"},
|
||||
{when: mustJST("2027-09-15 17:50"), kind: "P2R"},
|
||||
{when: mustJST("2027-11-30 18:22"), kind: "OPP"},
|
||||
},
|
||||
lastR2P: LastUranusRetrogradeToPrograde,
|
||||
nextR2P: NextUranusRetrogradeToPrograde,
|
||||
lastP2R: LastUranusProgradeToRetrograde,
|
||||
nextP2R: NextUranusProgradeToRetrograde,
|
||||
},
|
||||
{
|
||||
name: "Neptune",
|
||||
events: []stationEvent{
|
||||
{when: mustJST("2024-07-03 12:08"), kind: "P2R"},
|
||||
{when: mustJST("2024-09-21 09:17"), kind: "OPP"},
|
||||
{when: mustJST("2024-12-08 20:05"), kind: "R2P"},
|
||||
{when: mustJST("2025-07-05 23:30"), kind: "P2R"},
|
||||
{when: mustJST("2025-09-23 21:54"), kind: "OPP"},
|
||||
{when: mustJST("2025-12-11 09:21"), kind: "R2P"},
|
||||
{when: mustJST("2026-07-08 13:02"), kind: "P2R"},
|
||||
{when: mustJST("2026-09-26 10:36"), kind: "OPP"},
|
||||
{when: mustJST("2026-12-13 19:47"), kind: "R2P"},
|
||||
{when: mustJST("2027-07-11 01:06"), kind: "P2R"},
|
||||
{when: mustJST("2027-09-28 23:19"), kind: "OPP"},
|
||||
{when: mustJST("2027-12-16 07:16"), kind: "R2P"},
|
||||
},
|
||||
lastR2P: LastNeptuneRetrogradeToPrograde,
|
||||
nextR2P: NextNeptuneRetrogradeToPrograde,
|
||||
lastP2R: LastNeptuneProgradeToRetrograde,
|
||||
nextP2R: NextNeptuneProgradeToRetrograde,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
for i, event := range tc.events {
|
||||
switch event.kind {
|
||||
case "P2R":
|
||||
before := event.when.Add(-24 * time.Hour)
|
||||
after := event.when.Add(24 * time.Hour)
|
||||
nextP2R := JDE2DateByZone(tc.nextP2R(toUTJD(before)), event.when.Location(), false)
|
||||
lastP2R := JDE2DateByZone(tc.lastP2R(toUTJD(after)), event.when.Location(), false)
|
||||
if !sameMinute(nextP2R, event.when) {
|
||||
t.Fatalf("%s next P2R mismatch: got %s want %s", tc.name, nextP2R, event.when)
|
||||
}
|
||||
if !sameMinute(lastP2R, event.when) {
|
||||
t.Fatalf("%s last P2R mismatch: got %s want %s", tc.name, lastP2R, event.when)
|
||||
}
|
||||
case "R2P":
|
||||
before := event.when.Add(-24 * time.Hour)
|
||||
after := event.when.Add(24 * time.Hour)
|
||||
nextR2P := JDE2DateByZone(tc.nextR2P(toUTJD(before)), event.when.Location(), false)
|
||||
lastR2P := JDE2DateByZone(tc.lastR2P(toUTJD(after)), event.when.Location(), false)
|
||||
if !sameMinute(nextR2P, event.when) {
|
||||
t.Fatalf("%s next R2P mismatch: got %s want %s", tc.name, nextR2P, event.when)
|
||||
}
|
||||
if !sameMinute(lastR2P, event.when) {
|
||||
t.Fatalf("%s last R2P mismatch: got %s want %s", tc.name, lastR2P, event.when)
|
||||
}
|
||||
case "OPP":
|
||||
prev := nearestOfKindBefore(tc.events, i, "P2R")
|
||||
next := nearestOfKindAfter(tc.events, i, "R2P")
|
||||
if prev.IsZero() || next.IsZero() {
|
||||
continue
|
||||
}
|
||||
query := event.when
|
||||
lastP2R := JDE2DateByZone(tc.lastP2R(toUTJD(query)), query.Location(), false)
|
||||
nextR2P := JDE2DateByZone(tc.nextR2P(toUTJD(query)), query.Location(), false)
|
||||
if !sameMinute(lastP2R, prev) {
|
||||
t.Fatalf("%s opposition last P2R mismatch: got %s want %s", tc.name, lastP2R, prev)
|
||||
}
|
||||
if !sameMinute(nextR2P, next) {
|
||||
t.Fatalf("%s opposition next R2P mismatch: got %s want %s", tc.name, nextR2P, next)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func nearestOfKindBefore(events []stationEvent, idx int, kind string) time.Time {
|
||||
for i := idx - 1; i >= 0; i-- {
|
||||
if events[i].kind == kind {
|
||||
return events[i].when
|
||||
}
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func nearestOfKindAfter(events []stationEvent, idx int, kind string) time.Time {
|
||||
for i := idx + 1; i < len(events); i++ {
|
||||
if events[i].kind == kind {
|
||||
return events[i].when
|
||||
}
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func sameMinute(got, want time.Time) bool {
|
||||
diff := got.Sub(want)
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
return diff <= 2*time.Minute
|
||||
}
|
||||
+6055
File diff suppressed because it is too large
Load Diff
+1841
File diff suppressed because it is too large
Load Diff
+1967
File diff suppressed because it is too large
Load Diff
+2023
File diff suppressed because it is too large
Load Diff
+6622
File diff suppressed because it is too large
Load Diff
+1152
-1152
File diff suppressed because it is too large
Load Diff
+1722
-1722
File diff suppressed because it is too large
Load Diff
+1728
-1728
File diff suppressed because it is too large
Load Diff
+2597
File diff suppressed because it is too large
Load Diff
+1152
-1152
File diff suppressed because it is too large
Load Diff
+58
@@ -0,0 +1,58 @@
|
||||
[
|
||||
{
|
||||
"body": "mercury",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 268.524035973,
|
||||
"declination": -24.001291126,
|
||||
"ecliptic_longitude": 268.6516112,
|
||||
"ecliptic_latitude": -0.5700521
|
||||
},
|
||||
{
|
||||
"body": "venus",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 280.056455581,
|
||||
"declination": -23.622404581,
|
||||
"ecliptic_longitude": 279.2064734,
|
||||
"ecliptic_latitude": -0.5050645
|
||||
},
|
||||
{
|
||||
"body": "mars",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 283.879610807,
|
||||
"declination": -23.720007274,
|
||||
"ecliptic_longitude": 282.6881475,
|
||||
"ecliptic_latitude": -0.8911035
|
||||
},
|
||||
{
|
||||
"body": "jupiter",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 113.124332352,
|
||||
"declination": 21.979135798,
|
||||
"ecliptic_longitude": 111.3575894,
|
||||
"ecliptic_latitude": 0.2391257
|
||||
},
|
||||
{
|
||||
"body": "saturn",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 357.380959207,
|
||||
"declination": -3.596394732,
|
||||
"ecliptic_longitude": 356.1672313,
|
||||
"ecliptic_latitude": -2.2587419
|
||||
},
|
||||
{
|
||||
"body": "uranus",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 55.737099009,
|
||||
"declination": 19.509648526,
|
||||
"ecliptic_longitude": 57.9492508,
|
||||
"ecliptic_latitude": -0.1975992
|
||||
},
|
||||
{
|
||||
"body": "neptune",
|
||||
"input_utc": "2026-01-01T00:00:00Z",
|
||||
"right_ascension": 0.077612938,
|
||||
"declination": -1.418610222,
|
||||
"ecliptic_longitude": 359.5068407,
|
||||
"ecliptic_latitude": -1.3324096
|
||||
}
|
||||
]
|
||||
+1152
-1152
File diff suppressed because it is too large
Load Diff
+1152
-1152
File diff suppressed because it is too large
Load Diff
+1728
-1728
File diff suppressed because it is too large
Load Diff
+7
-38
@@ -87,53 +87,22 @@ func UranusApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthUranusAway(jd float64) float64 {
|
||||
x, y, z := AUranusXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(6, jd, -1)
|
||||
}
|
||||
|
||||
func UranusApparentLo(jd float64) float64 {
|
||||
x, y, z := AUranusXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AUranusXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(6, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func UranusApparentBo(jd float64) float64 {
|
||||
x, y, z := AUranusXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AUranusXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(6, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func UranusApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := AUranusXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AUranusXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(6, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func UranusMag(jd float64) float64 {
|
||||
|
||||
+66
-65
@@ -40,6 +40,28 @@ func uranusSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
||||
return sub
|
||||
}
|
||||
|
||||
func uranusRADerivative(jde, delta float64) float64 {
|
||||
sub := UranusApparentRa(jde+delta) - UranusApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func uranusRADerivativeN(jde, delta float64, n int) float64 {
|
||||
sub := UranusApparentRaN(jde+delta, n) - UranusApparentRaN(jde-delta, n)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
|
||||
func uranusConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||
//0=last 1=next
|
||||
daysPerDegree := URANUS_S_PERIOD / 360
|
||||
@@ -94,113 +116,92 @@ func uranusConjunction(jde, degree float64, next uint8) float64 {
|
||||
}
|
||||
|
||||
func LastUranusConjunction(jde float64) float64 {
|
||||
return uranusConjunction(jde, 0, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 0, uranusConjunction)
|
||||
}
|
||||
|
||||
func NextUranusConjunction(jde float64) float64 {
|
||||
return uranusConjunction(jde, 0, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 0, uranusConjunction)
|
||||
}
|
||||
|
||||
func LastUranusOpposition(jde float64) float64 {
|
||||
return uranusConjunction(jde, 180, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 180, uranusConjunction)
|
||||
}
|
||||
|
||||
func NextUranusOpposition(jde float64) float64 {
|
||||
return uranusConjunction(jde, 180, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 180, uranusConjunction)
|
||||
}
|
||||
|
||||
func NextUranusEasternQuadrature(jde float64) float64 {
|
||||
return uranusConjunction(jde, 90, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 90, uranusConjunction)
|
||||
}
|
||||
|
||||
func LastUranusEasternQuadrature(jde float64) float64 {
|
||||
return uranusConjunction(jde, 90, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 90, uranusConjunction)
|
||||
}
|
||||
|
||||
func NextUranusWesternQuadrature(jde float64) float64 {
|
||||
return uranusConjunction(jde, 270, 1)
|
||||
return inclusiveNextPhaseEvent(jde, 270, uranusConjunction)
|
||||
}
|
||||
|
||||
func LastUranusWesternQuadrature(jde float64) float64 {
|
||||
return uranusConjunction(jde, 270, 0)
|
||||
return inclusiveLastPhaseEvent(jde, 270, uranusConjunction)
|
||||
}
|
||||
|
||||
func uranusRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
||||
//0=last 1=next
|
||||
raRate := func(jde float64, delta float64) float64 {
|
||||
sub := UranusApparentRa(jde+delta) - UranusApparentRa(jde-delta)
|
||||
if sub > 180 {
|
||||
sub -= 360
|
||||
}
|
||||
if sub < -180 {
|
||||
sub += 360
|
||||
}
|
||||
return sub / (2 * delta)
|
||||
}
|
||||
jde = uranusConjunctionFull(jde, 180, 1)
|
||||
func uranusRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||
oppositionTT := TD2UT(oppositionJD, true)
|
||||
startTT := oppositionTT
|
||||
endTT := oppositionTT
|
||||
if searchBeforeOpposition {
|
||||
jde -= 60
|
||||
easternQuadratureUT := uranusConjunction(oppositionTT, 90, 0)
|
||||
startTT = TD2UT(easternQuadratureUT, true)
|
||||
} else {
|
||||
jde += 60
|
||||
westernQuadratureUT := uranusConjunction(oppositionTT, 270, 1)
|
||||
endTT = TD2UT(westernQuadratureUT, true)
|
||||
}
|
||||
for {
|
||||
currentRate := raRate(jde, 1.0/86400.0)
|
||||
if math.Abs(currentRate) > 0.55 {
|
||||
jde += 2
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
estimateJD := jde
|
||||
for {
|
||||
prevJD := estimateJD
|
||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
estimateJD = prevJD - rateValue/rateSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||
return raRate(jd, 0.5/86400.0)
|
||||
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||
return uranusRADerivativeN(jd, 1.0/86400.0, uranusEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return uranusRADerivative(jd, 0.5/86400.0)
|
||||
})
|
||||
return TD2UT(bestJD, false)
|
||||
}
|
||||
|
||||
func NextUranusRetrogradeToPrograde(jde float64) float64 {
|
||||
date := uranusRetrograde(jde, false)
|
||||
if date < jde {
|
||||
oppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||
return uranusRetrograde(oppositionJD+10, false)
|
||||
lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||
date := uranusRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
nextOppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||
return uranusRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||
}
|
||||
|
||||
func LastUranusRetrogradeToPrograde(jde float64) float64 {
|
||||
jde = uranusConjunctionFull(jde, 180, 0) - 10
|
||||
date := uranusRetrograde(jde, false)
|
||||
if date > jde {
|
||||
oppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||
return uranusRetrograde(oppositionJD-10, false)
|
||||
lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||
date := uranusRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
previousOppositionJD := uranusConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||
return uranusRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||
}
|
||||
|
||||
func NextUranusProgradeToRetrograde(jde float64) float64 {
|
||||
date := uranusRetrograde(jde, true)
|
||||
if date < jde {
|
||||
oppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||
return uranusRetrograde(oppositionJD+10, true)
|
||||
nextOppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||
date := uranusRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
followingOppositionJD := uranusConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||
return uranusRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||
}
|
||||
|
||||
func LastUranusProgradeToRetrograde(jde float64) float64 {
|
||||
jde = uranusConjunctionFull(jde, 180, 0) - 10
|
||||
date := uranusRetrograde(jde, true)
|
||||
if date > jde {
|
||||
oppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||
return uranusRetrograde(oppositionJD-10, true)
|
||||
nextOppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||
date := uranusRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
return date
|
||||
lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||
return uranusRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||
}
|
||||
|
||||
+7
-38
@@ -87,53 +87,22 @@ func VenusApparentRaDec(jd float64) (float64, float64) {
|
||||
}
|
||||
|
||||
func EarthVenusAway(jd float64) float64 {
|
||||
x, y, z := AVenusXYZ(jd)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
return planetEarthAwayExplicitN(2, jd, -1)
|
||||
}
|
||||
|
||||
func VenusApparentLo(jd float64) float64 {
|
||||
x, y, z := AVenusXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AVenusXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo
|
||||
geo, _ := planetApparentGeocentricPositionN(2, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func VenusApparentBo(jd float64) float64 {
|
||||
x, y, z := AVenusXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AVenusXYZ(jd - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,jd);
|
||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
||||
//lo+=Nutation2000Bi(jd);
|
||||
return bo
|
||||
geo, _ := planetApparentGeocentricPositionN(2, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func VenusApparentLoBo(jd float64) (float64, float64) {
|
||||
x, y, z := AVenusXYZ(jd)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AVenusXYZ(jd - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
||||
//bo+=GXCBo(lo,bo,jd);
|
||||
lo += Nutation2000Bi(jd)
|
||||
return lo, bo
|
||||
geo, _ := planetApparentGeocentricPositionN(2, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func VenusMag(jd float64) float64 {
|
||||
|
||||
+429
-158
@@ -3,6 +3,7 @@ package basic
|
||||
import (
|
||||
"math"
|
||||
|
||||
"b612.me/astro/planet"
|
||||
. "b612.me/astro/tools"
|
||||
)
|
||||
|
||||
@@ -33,6 +34,23 @@ func venusSunLongitudeDeltaN(jde float64, n int) float64 {
|
||||
return sub
|
||||
}
|
||||
|
||||
func venusConjunctionAngleDelta(diff float64) float64 {
|
||||
diff = Limit360(diff)
|
||||
if diff > 180 {
|
||||
diff -= 360
|
||||
}
|
||||
if diff < -180 {
|
||||
diff += 360
|
||||
}
|
||||
return diff
|
||||
}
|
||||
|
||||
func venusConjunctionHeliocentricDelta(jd, targetDeg float64, n int) float64 {
|
||||
planetLo := planet.WherePlanetN(2, 0, jd, n)
|
||||
earthLo := planet.WherePlanetN(-1, 0, jd, n)
|
||||
return venusConjunctionAngleDelta(planetLo - earthLo - targetDeg)
|
||||
}
|
||||
|
||||
func venusSunRADelta(jde float64) float64 {
|
||||
sub := Limit360(VenusApparentRa(jde) - SunApparentRa(jde))
|
||||
if sub > 180 {
|
||||
@@ -66,13 +84,94 @@ func venusRADerivativeN(jde, val float64, n int) float64 {
|
||||
return sub / (2 * val)
|
||||
}
|
||||
|
||||
func venusRAContinuousForMax(jde float64) float64 {
|
||||
ra := VenusApparentRa(jde)
|
||||
if ra < 180 {
|
||||
return ra + 360
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
func venusRAContinuousForMaxN(jde float64, n int) float64 {
|
||||
ra := VenusApparentRaN(jde, n)
|
||||
if ra < 180 {
|
||||
return ra + 360
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
func venusRAContinuousForMin(jde float64) float64 {
|
||||
ra := VenusApparentRa(jde)
|
||||
if ra > 180 {
|
||||
return ra - 360
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
func venusRAContinuousForMinN(jde float64, n int) float64 {
|
||||
ra := VenusApparentRaN(jde, n)
|
||||
if ra > 180 {
|
||||
return ra - 360
|
||||
}
|
||||
return ra
|
||||
}
|
||||
|
||||
func venusRAExtremumRefine(seed, start, end, step float64, fn func(float64) float64) float64 {
|
||||
centerJD := clampFloat64(seed, start, end)
|
||||
halfStep := step
|
||||
bestJD := centerJD
|
||||
bestVal := fn(centerJD)
|
||||
for i := 0; i < 8; i++ {
|
||||
leftJD := clampFloat64(centerJD-halfStep, start, end)
|
||||
rightJD := clampFloat64(centerJD+halfStep, start, end)
|
||||
leftVal := fn(leftJD)
|
||||
centerVal := fn(centerJD)
|
||||
rightVal := fn(rightJD)
|
||||
if leftVal > bestVal {
|
||||
bestVal = leftVal
|
||||
bestJD = leftJD
|
||||
}
|
||||
if centerVal > bestVal {
|
||||
bestVal = centerVal
|
||||
bestJD = centerJD
|
||||
}
|
||||
if rightVal > bestVal {
|
||||
bestVal = rightVal
|
||||
bestJD = rightJD
|
||||
}
|
||||
denominator := leftVal - 2*centerVal + rightVal
|
||||
if denominator == 0 {
|
||||
centerJD = bestJD
|
||||
halfStep /= 2
|
||||
continue
|
||||
}
|
||||
vertexJD := centerJD + 0.5*halfStep*(leftVal-rightVal)/denominator
|
||||
vertexJD = clampFloat64(vertexJD, leftJD, rightJD)
|
||||
vertexVal := fn(vertexJD)
|
||||
if vertexVal > bestVal {
|
||||
bestVal = vertexVal
|
||||
bestJD = vertexJD
|
||||
}
|
||||
centerJD = bestJD
|
||||
halfStep /= 2
|
||||
}
|
||||
return bestJD
|
||||
}
|
||||
|
||||
func venusSunElongationN(jde float64, n int) float64 {
|
||||
lo1, bo1 := VenusApparentLoBoN(jde, n)
|
||||
lo2 := SunApparentLo(jde)
|
||||
lo2 := HSunApparentLoN(jde, n)
|
||||
bo2 := HSunTrueBoN(jde, n)
|
||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||
}
|
||||
|
||||
func venusTrueElongationN(jde float64, n int) float64 {
|
||||
earth := mercuryHelioN(-1, jde, n)
|
||||
planetPos := mercuryHelioN(2, jde, n)
|
||||
geo := mercuryGeocentric(planetPos, earth)
|
||||
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
|
||||
}
|
||||
|
||||
func venusElongationDerivative(jde, val float64) float64 {
|
||||
sub := VenusSunElongation(jde+val) - VenusSunElongation(jde-val)
|
||||
if sub > 180 {
|
||||
@@ -96,91 +195,98 @@ func venusElongationDerivativeN(jde, val float64, n int) float64 {
|
||||
}
|
||||
|
||||
func venusConjunction(jde float64, next uint8) float64 {
|
||||
//0=last 1=next
|
||||
nowSub := venusSunLongitudeDeltaN(jde, venusEventSearchN)
|
||||
pos := math.Abs(venusSunLongitudeDeltaN(jde+1/86400.0, venusEventSearchN)) - math.Abs(nowSub)
|
||||
if pos >= 0 && next == 1 && nowSub > 0 {
|
||||
jde += VENUS_S_PERIOD/8.0 + 2
|
||||
queryTT := jde
|
||||
direction := -1.0
|
||||
if next == 1 {
|
||||
direction = 1
|
||||
}
|
||||
if pos >= 0 && next == 1 && nowSub < 0 {
|
||||
jde += VENUS_S_PERIOD/6.0 + 2
|
||||
}
|
||||
if pos <= 0 && next == 0 && nowSub < 0 {
|
||||
jde -= VENUS_S_PERIOD/8.0 + 2
|
||||
}
|
||||
if pos <= 0 && next == 0 && nowSub > 0 {
|
||||
jde -= VENUS_S_PERIOD/6.0 + 2
|
||||
}
|
||||
for {
|
||||
nowSub := venusSunLongitudeDeltaN(jde, venusEventSearchN)
|
||||
pos := math.Abs(venusSunLongitudeDeltaN(jde+1/86400.0, venusEventSearchN)) - math.Abs(nowSub)
|
||||
if math.Abs(nowSub) > 24 || (pos > 0 && next == 1) || (pos < 0 && next == 0) {
|
||||
if next == 1 {
|
||||
jde += 8
|
||||
} else {
|
||||
jde -= 8
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
JD1 := jde
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := venusSunLongitudeDelta(JD0)
|
||||
stDegreep := (venusSunLongitudeDelta(JD0+0.000005) - venusSunLongitudeDelta(JD0-0.000005)) / 0.00001
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
||||
break
|
||||
left := queryTT
|
||||
leftVal := venusSunLongitudeDeltaN(left, venusEventSearchN)
|
||||
if math.Abs(leftVal) <= 30.0/86400.0 {
|
||||
exact := eventZeroRefine(left, 1.0, 0.000005, venusSunLongitudeDelta)
|
||||
if math.Abs(exact-queryTT) <= 1.0 {
|
||||
return TD2UT(exact, false)
|
||||
}
|
||||
}
|
||||
return TD2UT(JD1, false)
|
||||
const step = 8.0
|
||||
for i := 0; i < 80; i++ {
|
||||
right := queryTT + direction*step*float64(i+1)
|
||||
rightVal := venusSunLongitudeDeltaN(right, venusEventSearchN)
|
||||
if leftVal == 0 || rightVal == 0 || leftVal*rightVal <= 0 {
|
||||
center := (left + right) / 2.0
|
||||
halfWindow := math.Abs(right-left) / 2.0
|
||||
return TD2UT(eventZeroRefine(center, halfWindow, 0.000005, venusSunLongitudeDelta), false)
|
||||
}
|
||||
left = right
|
||||
leftVal = rightVal
|
||||
}
|
||||
return TD2UT(eventZeroRefine(queryTT, VENUS_S_PERIOD, 0.000005, venusSunLongitudeDelta), false)
|
||||
}
|
||||
|
||||
func venusConjunctionTypeAt(eventUT float64) bool {
|
||||
return EarthVenusAway(eventUT) <= EarthAway(eventUT)
|
||||
}
|
||||
|
||||
func nextVenusTypedConjunctionFromEvent(jde float64, inferior bool) float64 {
|
||||
date := NextVenusConjunctionStrict(jde)
|
||||
if venusConjunctionTypeAt(date) == inferior {
|
||||
return date
|
||||
}
|
||||
return NextVenusConjunctionStrict(eventUTNextQueryTT(date))
|
||||
}
|
||||
|
||||
func lastVenusTypedConjunctionFromEvent(jde float64, inferior bool) float64 {
|
||||
date := LastVenusConjunctionStrict(jde)
|
||||
if venusConjunctionTypeAt(date) == inferior {
|
||||
return date
|
||||
}
|
||||
return LastVenusConjunctionStrict(eventUTLastQueryTT(date))
|
||||
}
|
||||
|
||||
func LastVenusConjunction(jde float64) float64 {
|
||||
return venusConjunction(jde, 0)
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusConjunctionStrict, NextVenusConjunctionStrict)
|
||||
}
|
||||
|
||||
func NextVenusConjunction(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusConjunctionStrict, NextVenusConjunctionStrict)
|
||||
}
|
||||
|
||||
func LastVenusConjunctionStrict(jde float64) float64 {
|
||||
return venusConjunction(jde, 0)
|
||||
}
|
||||
|
||||
func NextVenusConjunctionStrict(jde float64) float64 {
|
||||
return venusConjunction(jde, 1)
|
||||
}
|
||||
|
||||
func nextVenusTypedConjunction(jde float64, inferior bool) float64 {
|
||||
return nextVenusTypedConjunctionFromEvent(jde, inferior)
|
||||
}
|
||||
|
||||
func lastVenusTypedConjunction(jde float64, inferior bool) float64 {
|
||||
return lastVenusTypedConjunctionFromEvent(jde, inferior)
|
||||
}
|
||||
|
||||
func NextVenusInferiorConjunction(jde float64) float64 {
|
||||
date := NextVenusConjunction(jde)
|
||||
if EarthVenusAway(date) > EarthAway(date) {
|
||||
return NextVenusConjunction(date + 2)
|
||||
}
|
||||
return date
|
||||
return nextVenusTypedConjunction(jde, true)
|
||||
}
|
||||
|
||||
func NextVenusSuperiorConjunction(jde float64) float64 {
|
||||
date := NextVenusConjunction(jde)
|
||||
if EarthVenusAway(date) < EarthAway(date) {
|
||||
return NextVenusConjunction(date + 2)
|
||||
}
|
||||
return date
|
||||
return nextVenusTypedConjunction(jde, false)
|
||||
}
|
||||
|
||||
func LastVenusInferiorConjunction(jde float64) float64 {
|
||||
date := LastVenusConjunction(jde)
|
||||
if EarthVenusAway(date) > EarthAway(date) {
|
||||
return LastVenusConjunction(date - 2)
|
||||
}
|
||||
return date
|
||||
return lastVenusTypedConjunction(jde, true)
|
||||
}
|
||||
|
||||
func LastVenusSuperiorConjunction(jde float64) float64 {
|
||||
date := LastVenusConjunction(jde)
|
||||
if EarthVenusAway(date) < EarthAway(date) {
|
||||
return LastVenusConjunction(date - 2)
|
||||
}
|
||||
return date
|
||||
return lastVenusTypedConjunction(jde, false)
|
||||
}
|
||||
|
||||
func venusRetrograde(jde float64) float64 {
|
||||
//0=last 1=next
|
||||
lastHe := LastVenusConjunction(jde)
|
||||
nextHe := NextVenusConjunction(jde)
|
||||
lastHe := LastVenusConjunctionStrict(jde)
|
||||
nextHe := NextVenusConjunctionStrict(jde)
|
||||
nowSub := venusSunRADelta(jde)
|
||||
if nowSub > 0 {
|
||||
jde = lastHe + ((nextHe - lastHe) / 5.0 * 3.5)
|
||||
@@ -213,152 +319,317 @@ func venusRetrograde(jde float64) float64 {
|
||||
}
|
||||
|
||||
func NextVenusRetrograde(jde float64) float64 {
|
||||
date := venusRetrograde(jde)
|
||||
if date < jde {
|
||||
nextHe := NextVenusConjunction(jde)
|
||||
return venusRetrograde(nextHe + 2)
|
||||
p2r := NextVenusProgradeToRetrograde(jde)
|
||||
r2p := NextVenusRetrogradeToPrograde(jde)
|
||||
if sameEventJD(p2r, r2p) {
|
||||
return p2r
|
||||
}
|
||||
return date
|
||||
if p2r < r2p {
|
||||
return p2r
|
||||
}
|
||||
return r2p
|
||||
}
|
||||
|
||||
func LastVenusRetrograde(jde float64) float64 {
|
||||
lastHe := LastVenusConjunction(jde)
|
||||
date := venusRetrograde(lastHe + 2)
|
||||
if date > jde {
|
||||
lastLastHe := LastVenusConjunction(lastHe - 2)
|
||||
return venusRetrograde(lastLastHe + 2)
|
||||
p2r := LastVenusProgradeToRetrograde(jde)
|
||||
r2p := LastVenusRetrogradeToPrograde(jde)
|
||||
if sameEventJD(p2r, r2p) {
|
||||
return p2r
|
||||
}
|
||||
return date
|
||||
if p2r > r2p {
|
||||
return p2r
|
||||
}
|
||||
return r2p
|
||||
}
|
||||
|
||||
func venusStationInWindow(start, end float64, progradeToRetrograde bool) float64 {
|
||||
var best float64
|
||||
if progradeToRetrograde {
|
||||
guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
|
||||
return venusRAContinuousForMaxN(jd, venusEventSearchN)
|
||||
})
|
||||
best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
|
||||
return venusRAContinuousForMax(jd)
|
||||
})
|
||||
} else {
|
||||
guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
|
||||
return -venusRAContinuousForMinN(jd, venusEventSearchN)
|
||||
})
|
||||
best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
|
||||
return -venusRAContinuousForMin(jd)
|
||||
})
|
||||
}
|
||||
return TD2UT(best, false)
|
||||
}
|
||||
|
||||
func venusProgradeToRetrogradeAroundInferior(inferior float64) float64 {
|
||||
return venusStationInWindow(inferior-30.0, inferior-14.0, true)
|
||||
}
|
||||
|
||||
func venusRetrogradeToProgradeAroundInferior(inferior float64) float64 {
|
||||
return venusStationInWindow(inferior+14.0, inferior+24.0, false)
|
||||
}
|
||||
|
||||
func NextVenusProgradeToRetrograde(jde float64) float64 {
|
||||
date := NextVenusRetrograde(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return NextVenusRetrograde(date + VENUS_S_PERIOD/2)
|
||||
inferior := NextVenusInferiorConjunction(jde)
|
||||
for {
|
||||
date := venusProgradeToRetrogradeAroundInferior(inferior)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func NextVenusRetrogradeToPrograde(jde float64) float64 {
|
||||
date := NextVenusRetrograde(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return NextVenusRetrograde(date + 12)
|
||||
inferior := LastVenusInferiorConjunction(jde)
|
||||
for {
|
||||
date := venusRetrogradeToProgradeAroundInferior(inferior)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastVenusProgradeToRetrograde(jde float64) float64 {
|
||||
date := LastVenusRetrograde(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return LastVenusRetrograde(date - 12)
|
||||
inferior := NextVenusInferiorConjunction(jde)
|
||||
for {
|
||||
date := venusProgradeToRetrogradeAroundInferior(inferior)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func LastVenusRetrogradeToPrograde(jde float64) float64 {
|
||||
date := LastVenusRetrograde(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return LastVenusRetrograde(date - VENUS_S_PERIOD/2)
|
||||
inferior := LastVenusInferiorConjunction(jde)
|
||||
for {
|
||||
date := venusRetrogradeToProgradeAroundInferior(inferior)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||
}
|
||||
return date
|
||||
}
|
||||
|
||||
func VenusSunElongation(jde float64) float64 {
|
||||
lo1, bo1 := VenusApparentLoBo(jde)
|
||||
lo2 := SunApparentLo(jde)
|
||||
lo2 := HSunApparentLo(jde)
|
||||
bo2 := HSunTrueBo(jde)
|
||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||
}
|
||||
|
||||
func venusGreatestElongation(jde float64) float64 {
|
||||
lastHe := LastVenusConjunction(jde)
|
||||
nextHe := NextVenusConjunction(jde)
|
||||
nowSub := venusSunRADelta(jde)
|
||||
if nowSub > 0 {
|
||||
jde = lastHe + ((nextHe - lastHe) / 5.0 * 2.5)
|
||||
} else {
|
||||
jde = lastHe + ((nextHe - lastHe) / 5.0)
|
||||
}
|
||||
for {
|
||||
nowSub := venusElongationDerivativeN(jde, 1.0/86400.0, venusEventSearchN)
|
||||
if math.Abs(nowSub) > 0.15 {
|
||||
jde += 5
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
JD1 := jde
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := venusElongationDerivative(JD0, 2.0/86400.0)
|
||||
stDegreep := (venusElongationDerivative(JD0+15.0/86400.0, 2.0/86400.0) - venusElongationDerivative(JD0-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 30.0/86400.0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
min := eventZeroRefine(JD1, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||
return venusElongationDerivative(jd, 0.5/86400.0)
|
||||
func venusGreatestElongationInWindow(start, end float64) float64 {
|
||||
best := maximizeInWindow(start, end, 5.0, func(jd float64) float64 {
|
||||
return venusTrueElongationN(jd, venusEventSearchN)
|
||||
}, func(jd float64) float64 {
|
||||
return venusTrueElongationN(jd, -1)
|
||||
})
|
||||
//fmt.Println((min - lastHe) / (nextHe - lastHe))
|
||||
return TD2UT(min, false)
|
||||
return TD2UT(best, false)
|
||||
}
|
||||
|
||||
func venusEastElongationWindowEndingAt(inferior float64) (float64, float64) {
|
||||
lastSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(inferior))
|
||||
return lastSuperior + innerEventEpsilon, inferior - innerEventEpsilon
|
||||
}
|
||||
|
||||
func venusWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||
lastInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(superior))
|
||||
return lastInferior + innerEventEpsilon, superior - innerEventEpsilon
|
||||
}
|
||||
|
||||
func venusEastElongationWindowContaining(jde float64) (float64, float64) {
|
||||
nextInferior := NextVenusInferiorConjunction(jde)
|
||||
start, end := venusEastElongationWindowEndingAt(nextInferior)
|
||||
if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
|
||||
return start, end
|
||||
}
|
||||
currentInferior := LastVenusInferiorConjunction(jde)
|
||||
return venusEastElongationWindowEndingAt(currentInferior)
|
||||
}
|
||||
|
||||
func venusWestElongationWindowContaining(jde float64) (float64, float64) {
|
||||
nextSuperior := NextVenusSuperiorConjunction(jde)
|
||||
start, end := venusWestElongationWindowEndingAt(nextSuperior)
|
||||
if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
|
||||
return start, end
|
||||
}
|
||||
currentSuperior := LastVenusSuperiorConjunction(jde)
|
||||
return venusWestElongationWindowEndingAt(currentSuperior)
|
||||
}
|
||||
|
||||
func nextVenusGreatestElongationTyped(jde float64, east bool) float64 {
|
||||
if east {
|
||||
start, windowEnd := venusEastElongationWindowContaining(jde)
|
||||
for {
|
||||
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
nextInferior := NextVenusInferiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||
start, windowEnd = venusEastElongationWindowEndingAt(nextInferior)
|
||||
}
|
||||
}
|
||||
start, windowEnd := venusWestElongationWindowContaining(jde)
|
||||
for {
|
||||
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryAfterOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
nextSuperior := NextVenusSuperiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||
start, windowEnd = venusWestElongationWindowEndingAt(nextSuperior)
|
||||
}
|
||||
}
|
||||
|
||||
func lastVenusGreatestElongationTyped(jde float64, east bool) float64 {
|
||||
if east {
|
||||
start, windowEnd := venusEastElongationWindowContaining(jde)
|
||||
for {
|
||||
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
prevInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(start))
|
||||
start, windowEnd = venusEastElongationWindowEndingAt(prevInferior)
|
||||
}
|
||||
}
|
||||
start, windowEnd := venusWestElongationWindowContaining(jde)
|
||||
for {
|
||||
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||
return date
|
||||
}
|
||||
prevSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(start))
|
||||
start, windowEnd = venusWestElongationWindowEndingAt(prevSuperior)
|
||||
}
|
||||
}
|
||||
|
||||
func venusGreatestElongation(jde float64) float64 {
|
||||
east := venusSunRADelta(jde) > 0
|
||||
if east {
|
||||
return nextVenusGreatestElongationTyped(jde, true)
|
||||
}
|
||||
return nextVenusGreatestElongationTyped(jde, false)
|
||||
}
|
||||
|
||||
func NextVenusGreatestElongation(jde float64) float64 {
|
||||
date := venusGreatestElongation(jde)
|
||||
if date < jde {
|
||||
nextHe := NextVenusConjunction(jde)
|
||||
return venusGreatestElongation(nextHe + 2)
|
||||
east := NextVenusGreatestElongationEast(jde)
|
||||
west := NextVenusGreatestElongationWest(jde)
|
||||
if sameEventJD(east, west) {
|
||||
return east
|
||||
}
|
||||
return date
|
||||
if east < west {
|
||||
return east
|
||||
}
|
||||
return west
|
||||
}
|
||||
|
||||
func LastVenusGreatestElongation(jde float64) float64 {
|
||||
lastHe := LastVenusConjunction(jde)
|
||||
date := venusGreatestElongation(lastHe + 2)
|
||||
if date > jde {
|
||||
lastLastHe := LastVenusConjunction(lastHe - 2)
|
||||
return venusGreatestElongation(lastLastHe + 2)
|
||||
east := LastVenusGreatestElongationEast(jde)
|
||||
west := LastVenusGreatestElongationWest(jde)
|
||||
if sameEventJD(east, west) {
|
||||
return east
|
||||
}
|
||||
return date
|
||||
if east > west {
|
||||
return east
|
||||
}
|
||||
return west
|
||||
}
|
||||
|
||||
func LastVenusInferiorConjunctionInclusive(jde float64) float64 {
|
||||
date := LastVenusConjunction(jde)
|
||||
if venusConjunctionTypeAt(date) {
|
||||
return date
|
||||
}
|
||||
return LastVenusConjunction(eventUTLastQueryTT(date))
|
||||
}
|
||||
|
||||
func NextVenusInferiorConjunctionInclusive(jde float64) float64 {
|
||||
date := NextVenusConjunction(jde)
|
||||
if venusConjunctionTypeAt(date) {
|
||||
return date
|
||||
}
|
||||
return NextVenusConjunction(eventUTNextQueryTT(date))
|
||||
}
|
||||
|
||||
func LastVenusSuperiorConjunctionInclusive(jde float64) float64 {
|
||||
date := LastVenusConjunction(jde)
|
||||
if !venusConjunctionTypeAt(date) {
|
||||
return date
|
||||
}
|
||||
return LastVenusConjunction(eventUTLastQueryTT(date))
|
||||
}
|
||||
|
||||
func NextVenusSuperiorConjunctionInclusive(jde float64) float64 {
|
||||
date := NextVenusConjunction(jde)
|
||||
if !venusConjunctionTypeAt(date) {
|
||||
return date
|
||||
}
|
||||
return NextVenusConjunction(eventUTNextQueryTT(date))
|
||||
}
|
||||
|
||||
func LastVenusRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
|
||||
}
|
||||
|
||||
func NextVenusRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
|
||||
}
|
||||
|
||||
func LastVenusProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
|
||||
}
|
||||
|
||||
func NextVenusProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
|
||||
}
|
||||
|
||||
func LastVenusRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
|
||||
}
|
||||
|
||||
func NextVenusRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
|
||||
}
|
||||
|
||||
func LastVenusGreatestElongationInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
|
||||
}
|
||||
|
||||
func NextVenusGreatestElongationInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
|
||||
}
|
||||
|
||||
func LastVenusGreatestElongationEastInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
|
||||
}
|
||||
|
||||
func NextVenusGreatestElongationEastInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
|
||||
}
|
||||
|
||||
func LastVenusGreatestElongationWestInclusive(jde float64) float64 {
|
||||
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
|
||||
}
|
||||
|
||||
func NextVenusGreatestElongationWestInclusive(jde float64) float64 {
|
||||
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
|
||||
}
|
||||
|
||||
func NextVenusGreatestElongationEast(jde float64) float64 {
|
||||
date := NextVenusGreatestElongation(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return NextVenusGreatestElongation(date + 1)
|
||||
}
|
||||
return date
|
||||
return nextVenusGreatestElongationTyped(jde, true)
|
||||
}
|
||||
|
||||
func NextVenusGreatestElongationWest(jde float64) float64 {
|
||||
date := NextVenusGreatestElongation(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return NextVenusGreatestElongation(date + 1)
|
||||
}
|
||||
return date
|
||||
return nextVenusGreatestElongationTyped(jde, false)
|
||||
}
|
||||
|
||||
func LastVenusGreatestElongationEast(jde float64) float64 {
|
||||
date := LastVenusGreatestElongation(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub > 180 {
|
||||
return LastVenusGreatestElongation(date - 1)
|
||||
}
|
||||
return date
|
||||
return lastVenusGreatestElongationTyped(jde, true)
|
||||
}
|
||||
|
||||
func LastVenusGreatestElongationWest(jde float64) float64 {
|
||||
date := LastVenusGreatestElongation(jde)
|
||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
||||
if sub < 180 {
|
||||
return LastVenusGreatestElongation(date - 1)
|
||||
}
|
||||
return date
|
||||
return lastVenusGreatestElongationTyped(jde, false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user