feat(moon): 新增行星合月查询并修正月球地心赤经赤纬接口
- 修正月球地心真/视赤经赤纬接口口径 - 新增月球与七大行星合月时刻查询
This commit is contained in:
parent
34ff6a36ae
commit
be3af3884c
64
basic/moon_geocentric_apparent_external_test.go
Normal file
64
basic/moon_geocentric_apparent_external_test.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonGeocentricApparentSample struct {
|
||||||
|
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 TestMoonGeocentricApparentCoordinatesMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/moon_geocentric_apparent_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []moonGeocentricApparentSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
if len(samples) == 0 {
|
||||||
|
t.Fatal("empty moon apparent baseline")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
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 := "moon." + sample.InputUTC
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".RightAscension", HMoonGeocentricApparentRa(jd), sample.RightAscension, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".Declination", HMoonGeocentricApparentDec(jd), sample.Declination, 0.001)
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".EclipticLongitude", HMoonApparentLo(jd), sample.EclipticLongitude, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".EclipticLatitude", HMoonTrueBo(jd), sample.EclipticLatitude, 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonGeocentricTrueCoordinatesFollowDefinition(t *testing.T) {
|
||||||
|
samples := []time.Time{
|
||||||
|
time.Date(1900, 1, 14, 12, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1950, 6, 3, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2000, 2, 29, 18, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2100, 8, 17, 9, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := TD2UT(Date2JDE(sample.UTC()), true)
|
||||||
|
wantRA, wantDec := LoBoToRaDec(jd, HMoonTrueLo(jd), HMoonTrueBo(jd))
|
||||||
|
gotRA, gotDec := HMoonGeocentricTrueRaDec(jd)
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, sample.Format(time.RFC3339)+".TrueRightAscension", gotRA, wantRA, 1e-12)
|
||||||
|
assertPlanetPhaseClose(t, sample.Format(time.RFC3339)+".TrueDeclination", gotDec, wantDec, 1e-12)
|
||||||
|
}
|
||||||
|
}
|
||||||
30
basic/moon_geocentric_apparent_test.go
Normal file
30
basic/moon_geocentric_apparent_test.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHMoonGeocentricApparentRaDecComponentsMatch(t *testing.T) {
|
||||||
|
jd := TD2UT(JDECalc(2026, 1, 1.25), true)
|
||||||
|
|
||||||
|
ra, dec := HMoonGeocentricApparentRaDec(jd)
|
||||||
|
if diff := math.Abs(ra - HMoonGeocentricApparentRa(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, HMoonGeocentricApparentRa(jd))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - HMoonGeocentricApparentDec(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, HMoonGeocentricApparentDec(jd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHMoonGeocentricTrueRaDecComponentsMatch(t *testing.T) {
|
||||||
|
jd := TD2UT(JDECalc(2026, 1, 1.25), true)
|
||||||
|
|
||||||
|
ra, dec := HMoonGeocentricTrueRaDec(jd)
|
||||||
|
if diff := math.Abs(ra - HMoonGeocentricTrueRa(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, HMoonGeocentricTrueRa(jd))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - HMoonGeocentricTrueDec(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, HMoonGeocentricTrueDec(jd))
|
||||||
|
}
|
||||||
|
}
|
||||||
388
basic/moon_planet_conjunction.go
Normal file
388
basic/moon_planet_conjunction.go
Normal file
@ -0,0 +1,388 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
moonPlanetConjunctionEstimateN = 8
|
||||||
|
moonPlanetConjunctionNearQueryDeltaDeg = 3.0
|
||||||
|
moonPlanetConjunctionBracketStepDays = 0.5
|
||||||
|
moonPlanetConjunctionNearQueryStepDays = 0.25
|
||||||
|
moonPlanetConjunctionNearQueryHalfSpan = 1.5
|
||||||
|
moonPlanetConjunctionBracketHalfSpan = 2.0
|
||||||
|
moonPlanetConjunctionBracketGrowth = 2.0
|
||||||
|
moonPlanetConjunctionBracketAttempts = 3
|
||||||
|
moonPlanetConjunctionRefineStepDays = 0.5 / 86400.0
|
||||||
|
moonPlanetConjunctionEventTolerance = 0.01
|
||||||
|
moonPlanetConjunctionFallbackSpanScale = 1.5
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonPlanetConjunctionLocalResult struct {
|
||||||
|
lastUT float64
|
||||||
|
nextUT float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyMoonPlanetConjunctionLocalResult() moonPlanetConjunctionLocalResult {
|
||||||
|
return moonPlanetConjunctionLocalResult{
|
||||||
|
lastUT: math.NaN(),
|
||||||
|
nextUT: math.NaN(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonPlanetConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction events.
|
||||||
|
type MoonPlanetConjunctionPlanet int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MoonPlanetConjunctionMercury MoonPlanetConjunctionPlanet = iota + 1
|
||||||
|
MoonPlanetConjunctionVenus
|
||||||
|
MoonPlanetConjunctionMars
|
||||||
|
MoonPlanetConjunctionJupiter
|
||||||
|
MoonPlanetConjunctionSaturn
|
||||||
|
MoonPlanetConjunctionUranus
|
||||||
|
MoonPlanetConjunctionNeptune
|
||||||
|
)
|
||||||
|
|
||||||
|
func moonPlanetConjunctionWrappedDelta(diff float64) float64 {
|
||||||
|
diff = math.Mod(diff+180, 360)
|
||||||
|
if diff < 0 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
return diff - 180
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDeltaAt(jdTT float64, planet MoonPlanetConjunctionPlanet, n int) float64 {
|
||||||
|
moonRA := HMoonGeocentricApparentRaN(jdTT, n)
|
||||||
|
var planetRA float64
|
||||||
|
switch planet {
|
||||||
|
case MoonPlanetConjunctionMercury:
|
||||||
|
planetRA = MercuryApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionVenus:
|
||||||
|
planetRA = VenusApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionMars:
|
||||||
|
planetRA = MarsApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionJupiter:
|
||||||
|
planetRA = JupiterApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionSaturn:
|
||||||
|
planetRA = SaturnApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionUranus:
|
||||||
|
planetRA = UranusApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionNeptune:
|
||||||
|
planetRA = NeptuneApparentRaN(jdTT, n)
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionWrappedDelta(moonRA - planetRA)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionPeriodDays(planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
switch planet {
|
||||||
|
case MoonPlanetConjunctionMercury:
|
||||||
|
return 28.1
|
||||||
|
case MoonPlanetConjunctionVenus:
|
||||||
|
return 28.4
|
||||||
|
case MoonPlanetConjunctionMars:
|
||||||
|
return 29.2
|
||||||
|
case MoonPlanetConjunctionJupiter:
|
||||||
|
return 28.0
|
||||||
|
case MoonPlanetConjunctionSaturn:
|
||||||
|
return 27.4
|
||||||
|
case MoonPlanetConjunctionUranus:
|
||||||
|
return 27.3
|
||||||
|
case MoonPlanetConjunctionNeptune:
|
||||||
|
return 27.3
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionInDirection(eventUT, queryTT float64, direction int) bool {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
return eventUTQueryBeforeOrEqual(eventUT, queryTT)
|
||||||
|
case 1:
|
||||||
|
return eventUTQueryAfterOrEqual(eventUT, queryTT)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionFindBracket(centerTT, halfSpan, step float64, planet MoonPlanetConjunctionPlanet) (float64, float64, bool) {
|
||||||
|
if math.IsNaN(centerTT) || math.IsNaN(halfSpan) || math.IsNaN(step) || halfSpan <= 0 || step <= 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
start := centerTT - halfSpan
|
||||||
|
end := centerTT + halfSpan
|
||||||
|
samples := int(math.Ceil((end-start)/step)) + 1
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
if prevVal == 0 {
|
||||||
|
return prevTT, prevTT, true
|
||||||
|
}
|
||||||
|
bestLeft := 0.0
|
||||||
|
bestRight := 0.0
|
||||||
|
bestDistance := math.Inf(1)
|
||||||
|
for i := 1; i <= samples; i++ {
|
||||||
|
tt := start + float64(i)*step
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
if val == 0 {
|
||||||
|
return tt, tt, true
|
||||||
|
}
|
||||||
|
if prevVal*val < 0 {
|
||||||
|
mid := (prevTT + tt) / 2.0
|
||||||
|
distance := math.Abs(mid - centerTT)
|
||||||
|
if distance < bestDistance {
|
||||||
|
bestLeft = prevTT
|
||||||
|
bestRight = tt
|
||||||
|
bestDistance = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
if math.IsInf(bestDistance, 1) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return bestLeft, bestRight, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionRefineBracket(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
if leftTT > rightTT {
|
||||||
|
leftTT, rightTT = rightTT, leftTT
|
||||||
|
}
|
||||||
|
if leftTT == rightTT {
|
||||||
|
return leftTT
|
||||||
|
}
|
||||||
|
center := (leftTT + rightTT) / 2.0
|
||||||
|
halfWindow := (rightTT - leftTT) / 2.0
|
||||||
|
return eventZeroRefine(center, halfWindow, moonPlanetConjunctionRefineStepDays, func(sampleTT float64) float64 {
|
||||||
|
return moonPlanetConjunctionDeltaAt(sampleTT, planet, -1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionEventUT(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
eventTT := moonPlanetConjunctionRefineBracket(leftTT, rightTT, planet)
|
||||||
|
if math.Abs(moonPlanetConjunctionDeltaAt(eventTT, planet, -1)) > moonPlanetConjunctionEventTolerance {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return TD2UT(eventTT, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionCollectLocalEvent(result *moonPlanetConjunctionLocalResult, queryTT, eventUT float64) {
|
||||||
|
if math.IsNaN(eventUT) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if eventUTQueryBeforeOrEqual(eventUT, queryTT) {
|
||||||
|
if math.IsNaN(result.lastUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.lastUT, queryTT)) {
|
||||||
|
result.lastUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if eventUTQueryAfterOrEqual(eventUT, queryTT) {
|
||||||
|
if math.IsNaN(result.nextUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.nextUT, queryTT)) {
|
||||||
|
result.nextUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionShouldCheckLocal(queryTT float64, planet MoonPlanetConjunctionPlanet) bool {
|
||||||
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
|
||||||
|
if math.IsNaN(delta) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return math.Abs(delta) <= moonPlanetConjunctionNearQueryDeltaDeg
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
|
||||||
|
result := emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
start := queryTT - moonPlanetConjunctionNearQueryHalfSpan
|
||||||
|
end := queryTT + moonPlanetConjunctionNearQueryHalfSpan
|
||||||
|
step := moonPlanetConjunctionNearQueryStepDays
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
samples := int(math.Ceil((end-start)/step)) + 1
|
||||||
|
for i := 1; i <= samples; i++ {
|
||||||
|
tt := start + float64(i)*step
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
}
|
||||||
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
||||||
|
moonPlanetConjunctionCollectLocalEvent(&result, queryTT, moonPlanetConjunctionEventUT(prevTT, tt, planet))
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionMaybeLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
|
||||||
|
if !moonPlanetConjunctionShouldCheckLocal(queryTT, planet) {
|
||||||
|
return emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionLocalEvents(queryTT, planet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionGuessTT(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
|
||||||
|
if math.IsNaN(delta) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
period := moonPlanetConjunctionPeriodDays(planet)
|
||||||
|
if math.IsNaN(period) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
return queryTT - innerLastCycleOffset(delta, period)
|
||||||
|
case 1:
|
||||||
|
return queryTT + innerNextCycleOffset(delta, period)
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalFallback(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
period := moonPlanetConjunctionPeriodDays(planet)
|
||||||
|
if math.IsNaN(period) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
span := period * moonPlanetConjunctionFallbackSpanScale
|
||||||
|
if span <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
step := moonPlanetConjunctionNearQueryStepDays
|
||||||
|
start := queryTT
|
||||||
|
end := queryTT
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
start -= span
|
||||||
|
case 1:
|
||||||
|
end += span
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
bestEventUT := math.NaN()
|
||||||
|
for tt := start + step; ; tt += step {
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
||||||
|
eventUT := moonPlanetConjunctionEventUT(prevTT, tt, planet)
|
||||||
|
if !math.IsNaN(eventUT) && moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
||||||
|
if direction == 1 {
|
||||||
|
return eventUT
|
||||||
|
}
|
||||||
|
bestEventUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
return bestEventUT
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalEventWithLocal(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, local moonPlanetConjunctionLocalResult) float64 {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
if !math.IsNaN(local.lastUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if !math.IsNaN(local.nextUT) {
|
||||||
|
return local.nextUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guessTT := moonPlanetConjunctionGuessTT(queryTT, planet, direction)
|
||||||
|
if math.IsNaN(guessTT) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
halfSpan := moonPlanetConjunctionBracketHalfSpan
|
||||||
|
for attempt := 0; attempt < moonPlanetConjunctionBracketAttempts; attempt++ {
|
||||||
|
left, right, ok := moonPlanetConjunctionFindBracket(guessTT, halfSpan, moonPlanetConjunctionBracketStepDays, planet)
|
||||||
|
if ok {
|
||||||
|
eventUT := moonPlanetConjunctionEventUT(left, right, planet)
|
||||||
|
if math.IsNaN(eventUT) {
|
||||||
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
||||||
|
return eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionDirectionalFallback(queryTT, planet, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalEvent(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEventWithLocal(queryTT, planet, direction, moonPlanetConjunctionMaybeLocalEvents(queryTT, planet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMoonPlanetConjunction 指定时刻之前最近一次行星合月(赤经合) / previous Moon-planet conjunction at or before jde.
|
||||||
|
func LastMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMoonPlanetConjunction 指定时刻之后最近一次行星合月(赤经合) / next Moon-planet conjunction at or after jde.
|
||||||
|
func NextMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestMoonPlanetConjunction 离指定时刻最近一次行星合月(赤经合) / closest Moon-planet conjunction to jde.
|
||||||
|
func ClosestMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
local := moonPlanetConjunctionMaybeLocalEvents(jde, planet)
|
||||||
|
if !math.IsNaN(local.lastUT) && !math.IsNaN(local.nextUT) {
|
||||||
|
if sameEventJD(local.lastUT, local.nextUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
return closestEventUTToQueryTT(jde, local.lastUT, local.nextUT)
|
||||||
|
}
|
||||||
|
if !math.IsNaN(local.lastUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
if !math.IsNaN(local.nextUT) {
|
||||||
|
return local.nextUT
|
||||||
|
}
|
||||||
|
last := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, -1, local)
|
||||||
|
next := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, 1, local)
|
||||||
|
if math.IsNaN(last) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if math.IsNaN(next) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return closestEventUTToQueryTT(jde, last, next)
|
||||||
|
}
|
||||||
260
basic/moon_planet_conjunction_external_test.go
Normal file
260
basic/moon_planet_conjunction_external_test.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonPlanetConjunctionBaselineSample struct {
|
||||||
|
Planet string `json:"planet"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Month int `json:"month"`
|
||||||
|
TimeUTC string `json:"time_utc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonPlanetConjunctionBaseline struct {
|
||||||
|
Samples []moonPlanetConjunctionBaselineSample `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoonPlanetConjunctionBaseline(t *testing.T) moonPlanetConjunctionBaseline {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := [][]string{
|
||||||
|
{
|
||||||
|
"testdata/moon_planet_conjunction_baseline.json",
|
||||||
|
"basic/testdata/moon_planet_conjunction_baseline.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/moon_planet_conjunction_baseline_samples.json",
|
||||||
|
"basic/testdata/moon_planet_conjunction_baseline_samples.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged moonPlanetConjunctionBaseline
|
||||||
|
for index, candidates := range paths {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, path := range candidates {
|
||||||
|
data, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
var baseline moonPlanetConjunctionBaseline
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatalf("decode baseline %s: %v", path, err)
|
||||||
|
}
|
||||||
|
merged.Samples = append(merged.Samples, baseline.Samples...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && index == 0 {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(merged.Samples) == 0 {
|
||||||
|
t.Fatal("empty moon-planet conjunction baseline")
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionsMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
baseline := loadMoonPlanetConjunctionBaseline(t)
|
||||||
|
|
||||||
|
type conjunctionCase struct {
|
||||||
|
planet MoonPlanetConjunctionPlanet
|
||||||
|
next func(float64, MoonPlanetConjunctionPlanet) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]conjunctionCase{
|
||||||
|
"mercury": {planet: MoonPlanetConjunctionMercury, next: NextMoonPlanetConjunction},
|
||||||
|
"venus": {planet: MoonPlanetConjunctionVenus, next: NextMoonPlanetConjunction},
|
||||||
|
"mars": {planet: MoonPlanetConjunctionMars, next: NextMoonPlanetConjunction},
|
||||||
|
"jupiter": {planet: MoonPlanetConjunctionJupiter, next: NextMoonPlanetConjunction},
|
||||||
|
"saturn": {planet: MoonPlanetConjunctionSaturn, next: NextMoonPlanetConjunction},
|
||||||
|
"uranus": {planet: MoonPlanetConjunctionUranus, next: NextMoonPlanetConjunction},
|
||||||
|
"neptune": {planet: MoonPlanetConjunctionNeptune, next: NextMoonPlanetConjunction},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tolerance = 20 * time.Second
|
||||||
|
var maxDiff time.Duration
|
||||||
|
|
||||||
|
seen := make(map[string]int, len(cases))
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
tc, ok := cases[sample.Planet]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown planet %q", sample.Planet)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
queryTT := TD2UT(Date2JDE(wantTime.Add(-12*time.Hour).UTC()), true)
|
||||||
|
gotUT := tc.next(queryTT, tc.planet)
|
||||||
|
gotTime := JDE2DateByZone(gotUT, time.UTC, false)
|
||||||
|
diff := gotTime.Sub(wantTime)
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
if diff > maxDiff {
|
||||||
|
maxDiff = diff
|
||||||
|
}
|
||||||
|
if diff > tolerance {
|
||||||
|
t.Fatalf("%s %04d-%02d time mismatch: got %s want %s tolerance %v", sample.Planet, sample.Year, sample.Month, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, tolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), tc.planet, -1))
|
||||||
|
if delta > 0.01 {
|
||||||
|
t.Fatalf("%s %04d-%02d event not near conjunction: delta=%.8f deg", sample.Planet, sample.Year, sample.Month, delta)
|
||||||
|
}
|
||||||
|
seen[sample.Planet]++
|
||||||
|
}
|
||||||
|
|
||||||
|
for planet := range cases {
|
||||||
|
if seen[planet] == 0 {
|
||||||
|
t.Fatalf("missing baseline samples for %s", planet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("moon-planet conjunction max diff: time=%v", maxDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionDirectionalConsistencyAroundBaseline(t *testing.T) {
|
||||||
|
baseline := loadMoonPlanetConjunctionBaseline(t)
|
||||||
|
|
||||||
|
planets := map[string]MoonPlanetConjunctionPlanet{
|
||||||
|
"mercury": MoonPlanetConjunctionMercury,
|
||||||
|
"venus": MoonPlanetConjunctionVenus,
|
||||||
|
"mars": MoonPlanetConjunctionMars,
|
||||||
|
"jupiter": MoonPlanetConjunctionJupiter,
|
||||||
|
"saturn": MoonPlanetConjunctionSaturn,
|
||||||
|
"uranus": MoonPlanetConjunctionUranus,
|
||||||
|
"neptune": MoonPlanetConjunctionNeptune,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
planet, ok := planets[sample.Planet]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown planet %q", sample.Planet)
|
||||||
|
}
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
queryAtTT := TD2UT(Date2JDE(wantTime.UTC()), true)
|
||||||
|
queryAfterTT := TD2UT(Date2JDE(wantTime.Add(time.Hour).UTC()), true)
|
||||||
|
|
||||||
|
exactNext := NextMoonPlanetConjunction(queryAtTT, planet)
|
||||||
|
exactClosest := ClosestMoonPlanetConjunction(queryAtTT, planet)
|
||||||
|
exactLastAfter := LastMoonPlanetConjunction(queryAfterTT, planet)
|
||||||
|
|
||||||
|
wantUT := Date2JDE(wantTime.UTC())
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"exactNext": exactNext,
|
||||||
|
"exactClosest": exactClosest,
|
||||||
|
"lastAfterEvent": exactLastAfter,
|
||||||
|
} {
|
||||||
|
gotTime := JDE2DateByZone(gotUT, time.UTC, false)
|
||||||
|
if diff := math.Abs(gotUT - wantUT); diff > 5.0/86400.0 {
|
||||||
|
t.Fatalf("%s %s mismatch: got %s want %s diff=%v", sample.Planet, name, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, diff*86400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionRejectsOppositionBranchJump(t *testing.T) {
|
||||||
|
query := time.Date(1900, 11, 10, 12, 0, 0, 0, time.UTC)
|
||||||
|
queryTT := TD2UT(Date2JDE(query), true)
|
||||||
|
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
|
||||||
|
if math.Abs(lastUT-Date2JDE(query)) <= 5.0/86400.0 {
|
||||||
|
t.Fatalf("last returned query time on branch jump: got %s", JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if math.Abs(nextUT-Date2JDE(query)) <= 5.0/86400.0 {
|
||||||
|
t.Fatalf("next returned query time on branch jump: got %s", JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"last": lastUT,
|
||||||
|
"next": nextUT,
|
||||||
|
} {
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), MoonPlanetConjunctionSaturn, -1))
|
||||||
|
if delta > moonPlanetConjunctionEventTolerance {
|
||||||
|
t.Fatalf("%s returned non-event candidate: delta=%.8f event=%s", name, delta, JDE2DateByZone(gotUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionDirectionalOrderingOnSampleQueries(t *testing.T) {
|
||||||
|
samples := []struct {
|
||||||
|
planet MoonPlanetConjunctionPlanet
|
||||||
|
query time.Time
|
||||||
|
}{
|
||||||
|
{planet: MoonPlanetConjunctionSaturn, query: time.Date(1700, 4, 15, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionMercury, query: time.Date(1900, 1, 14, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionVenus, query: time.Date(1950, 6, 3, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionMars, query: time.Date(2000, 2, 29, 18, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionJupiter, query: time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionSaturn, query: time.Date(2100, 8, 17, 6, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionUranus, query: time.Date(2200, 11, 2, 9, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionNeptune, query: time.Date(2300, 4, 24, 3, 0, 0, 0, time.UTC)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
queryTT := TD2UT(Date2JDE(sample.query.UTC()), true)
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
closestUT := ClosestMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
|
||||||
|
if math.IsNaN(lastUT) || math.IsNaN(nextUT) || math.IsNaN(closestUT) {
|
||||||
|
t.Fatalf("planet=%v query=%s returned NaN event(s): last=%v next=%v closest=%v", sample.planet, sample.query.Format(time.RFC3339), lastUT, nextUT, closestUT)
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(lastUT, queryTT) {
|
||||||
|
t.Fatalf("planet=%v last after query: last=%s query=%s", sample.planet, JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano), sample.query.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(nextUT, queryTT) {
|
||||||
|
t.Fatalf("planet=%v next before query: next=%s query=%s", sample.planet, JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano), sample.query.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if closestUT != closestEventUTToQueryTT(queryTT, lastUT, nextUT) {
|
||||||
|
t.Fatalf("planet=%v closest mismatch: got=%s want=%s", sample.planet, JDE2DateByZone(closestUT, time.UTC, false).Format(time.RFC3339Nano), JDE2DateByZone(closestEventUTToQueryTT(queryTT, lastUT, nextUT), time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"last": lastUT,
|
||||||
|
"next": nextUT,
|
||||||
|
"closest": closestUT,
|
||||||
|
} {
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), sample.planet, -1))
|
||||||
|
if delta > moonPlanetConjunctionEventTolerance {
|
||||||
|
t.Fatalf("planet=%v %s returned non-event candidate: delta=%.8f event=%s", sample.planet, name, delta, JDE2DateByZone(gotUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionKeepsImmediateNeighborEvents(t *testing.T) {
|
||||||
|
query := time.Date(1700, 4, 15, 12, 0, 0, 0, time.UTC)
|
||||||
|
queryTT := TD2UT(Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
closestUT := ClosestMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
|
||||||
|
wantLast := time.Date(1700, 4, 15, 11, 55, 59, 115569293, time.UTC)
|
||||||
|
wantNext := time.Date(1700, 5, 13, 0, 35, 5, 981616675, time.UTC)
|
||||||
|
const tolerance = 5.0 / 86400.0
|
||||||
|
|
||||||
|
if diff := math.Abs(lastUT - Date2JDE(wantLast)); diff > tolerance {
|
||||||
|
t.Fatalf("last mismatch: got=%s want=%s diff=%.3fs", JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano), wantLast.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
if diff := math.Abs(nextUT - Date2JDE(wantNext)); diff > tolerance {
|
||||||
|
t.Fatalf("next mismatch: got=%s want=%s diff=%.3fs", JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano), wantNext.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
if !sameEventJD(closestUT, lastUT) {
|
||||||
|
t.Fatalf("closest should keep immediate previous event: closest=%s last=%s", JDE2DateByZone(closestUT, time.UTC, false).Format(time.RFC3339Nano), JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -126,6 +126,68 @@ func HMoonApparentLoN(jd float64, n int) float64 {
|
|||||||
return HMoonTrueLoN(jd, n) + Nutation2000Bi(jd)
|
return HMoonTrueLoN(jd, n) + Nutation2000Bi(jd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRa 月亮地心视赤经 / apparent geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricApparentRa(jd float64) float64 {
|
||||||
|
return HMoonGeocentricApparentRaN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRaN 月亮地心视赤经(截断版) / truncated apparent geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricApparentRaN(jd float64, n int) float64 {
|
||||||
|
return LoToRa(jd, HMoonApparentLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentDec 月亮地心视赤纬 / apparent geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentDec(jd float64) float64 {
|
||||||
|
return HMoonGeocentricApparentDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentDecN 月亮地心视赤纬(截断版) / truncated apparent geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentDecN(jd float64, n int) float64 {
|
||||||
|
return ArcSin(Sin(HMoonTrueBoN(jd, n))*Cos(TrueObliquity(jd)) +
|
||||||
|
Cos(HMoonTrueBoN(jd, n))*Sin(TrueObliquity(jd))*Sin(HMoonApparentLoN(jd, n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRaDec 月亮地心视赤经、视赤纬 / apparent geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentRaDec(jd float64) (float64, float64) {
|
||||||
|
return HMoonGeocentricApparentRaDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRaDecN 月亮地心视赤经、视赤纬(截断版) / truncated apparent geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentRaDecN(jd float64, n int) (float64, float64) {
|
||||||
|
return LoBoToRaDec(jd, HMoonApparentLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRa 月亮地心真赤经 / true geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricTrueRa(jd float64) float64 {
|
||||||
|
return HMoonGeocentricTrueRaN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRaN 月亮地心真赤经(截断版) / truncated true geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricTrueRaN(jd float64, n int) float64 {
|
||||||
|
return LoToRa(jd, HMoonTrueLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueDec 月亮地心真赤纬 / true geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueDec(jd float64) float64 {
|
||||||
|
return HMoonGeocentricTrueDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueDecN 月亮地心真赤纬(截断版) / truncated true geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueDecN(jd float64, n int) float64 {
|
||||||
|
return ArcSin(Sin(HMoonTrueBoN(jd, n))*Cos(TrueObliquity(jd)) +
|
||||||
|
Cos(HMoonTrueBoN(jd, n))*Sin(TrueObliquity(jd))*Sin(HMoonTrueLoN(jd, n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRaDec 月亮地心真赤经、真赤纬 / true geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueRaDec(jd float64) (float64, float64) {
|
||||||
|
return HMoonGeocentricTrueRaDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRaDecN 月亮地心真赤经、真赤纬(截断版) / truncated true geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueRaDecN(jd float64, n int) (float64, float64) {
|
||||||
|
return LoBoToRaDec(jd, HMoonTrueLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
func HMoonTrueRaDec(jd float64) (float64, float64) {
|
func HMoonTrueRaDec(jd float64) (float64, float64) {
|
||||||
return HMoonTrueRaDecN(jd, -1)
|
return HMoonTrueRaDecN(jd, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
34
basic/testdata/moon_geocentric_apparent_baseline.json
vendored
Normal file
34
basic/testdata/moon_geocentric_apparent_baseline.json
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 63.920306258,
|
||||||
|
"declination": 26.403701421,
|
||||||
|
"ecliptic_longitude": 66.7156363,
|
||||||
|
"ecliptic_latitude": 5.0490966
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"input_utc": "2026-05-01T00:00:00Z",
|
||||||
|
"right_ascension": 208.849626532,
|
||||||
|
"declination": -16.256039871,
|
||||||
|
"ecliptic_longitude": 212.5315932,
|
||||||
|
"ecliptic_latitude": -4.1622995
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"input_utc": "2026-08-29T00:00:00Z",
|
||||||
|
"right_ascension": 346.062573698,
|
||||||
|
"declination": -4.416718092,
|
||||||
|
"ecliptic_longitude": 345.4608613,
|
||||||
|
"ecliptic_latitude": 1.4247855
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"input_utc": "2026-12-27T00:00:00Z",
|
||||||
|
"right_ascension": 139.195692903,
|
||||||
|
"declination": 16.272137989,
|
||||||
|
"ecliptic_longitude": 136.6058459,
|
||||||
|
"ecliptic_latitude": 0.4338603
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
94
basic/testdata/moon_planet_conjunction_baseline.json
vendored
Normal file
94
basic/testdata/moon_planet_conjunction_baseline.json
vendored
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"samples": [
|
||||||
|
{"planet":"mercury","year":2026,"month":1,"time_utc":"2026-01-18T15:06:38Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":2,"time_utc":"2026-02-18T23:02:34Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":3,"time_utc":"2026-03-17T14:07:16Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":4,"time_utc":"2026-04-15T19:11:09Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":5,"time_utc":"2026-05-17T02:50:17Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":6,"time_utc":"2026-06-16T19:32:07Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":7,"time_utc":"2026-07-14T04:37:03Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":8,"time_utc":"2026-08-11T12:47:22Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":9,"time_utc":"2026-09-12T07:28:45Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":10,"time_utc":"2026-10-12T20:08:25Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":11,"time_utc":"2026-11-08T16:33:09Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":12,"time_utc":"2026-12-07T22:03:14Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":1,"time_utc":"2026-01-19T01:01:00Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":2,"time_utc":"2026-02-18T09:20:04Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":3,"time_utc":"2026-03-20T12:37:37Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":4,"time_utc":"2026-04-19T08:47:17Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":5,"time_utc":"2026-05-19T01:49:30Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":6,"time_utc":"2026-06-17T20:20:35Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":7,"time_utc":"2026-07-17T16:31:26Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":8,"time_utc":"2026-08-16T08:47:10Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":9,"time_utc":"2026-09-14T11:10:57Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":10,"time_utc":"2026-10-12T02:31:37Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":11,"time_utc":"2026-11-07T11:33:28Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":12,"time_utc":"2026-12-05T10:44:42Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":1,"time_utc":"2026-01-18T14:09:59Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":2,"time_utc":"2026-02-16T17:40:45Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":3,"time_utc":"2026-03-17T21:52:35Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":4,"time_utc":"2026-04-16T00:46:20Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":5,"time_utc":"2026-05-15T00:44:20Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":6,"time_utc":"2026-06-12T21:15:20Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":7,"time_utc":"2026-07-11T14:38:57Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":8,"time_utc":"2026-08-09T05:31:55Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":9,"time_utc":"2026-09-06T18:25:43Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":10,"time_utc":"2026-10-05T05:31:58Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":11,"time_utc":"2026-11-02T14:25:02Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":11,"time_utc":"2026-11-30T19:34:48Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":12,"time_utc":"2026-12-28T17:44:28Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":1,"time_utc":"2026-01-03T21:59:20Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":1,"time_utc":"2026-01-31T02:29:07Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":2,"time_utc":"2026-02-27T06:24:21Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":3,"time_utc":"2026-03-26T12:11:13Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":4,"time_utc":"2026-04-22T22:03:39Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":5,"time_utc":"2026-05-20T12:36:55Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":6,"time_utc":"2026-06-17T06:51:28Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":7,"time_utc":"2026-07-15T03:03:33Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":8,"time_utc":"2026-08-11T23:22:55Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":9,"time_utc":"2026-09-08T18:11:18Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":10,"time_utc":"2026-10-06T10:16:20Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":11,"time_utc":"2026-11-02T23:09:31Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":11,"time_utc":"2026-11-30T09:16:19Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":12,"time_utc":"2026-12-27T17:30:35Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":1,"time_utc":"2026-01-23T12:40:10Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":2,"time_utc":"2026-02-20T00:03:21Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":3,"time_utc":"2026-03-19T14:12:20Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":4,"time_utc":"2026-04-16T06:08:13Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":5,"time_utc":"2026-05-13T21:58:07Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":6,"time_utc":"2026-06-10T11:41:01Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":7,"time_utc":"2026-07-07T21:49:36Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":8,"time_utc":"2026-08-04T04:10:47Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":8,"time_utc":"2026-08-31T08:07:26Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":9,"time_utc":"2026-09-27T12:00:54Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":10,"time_utc":"2026-10-24T17:41:49Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":11,"time_utc":"2026-11-21T01:26:46Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":12,"time_utc":"2026-12-18T10:18:35Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":1,"time_utc":"2026-01-27T18:46:51Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":2,"time_utc":"2026-02-24T00:35:51Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":3,"time_utc":"2026-03-23T07:40:49Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":4,"time_utc":"2026-04-19T17:35:45Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":5,"time_utc":"2026-05-17T05:58:41Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":6,"time_utc":"2026-06-13T19:08:48Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":7,"time_utc":"2026-07-11T07:07:53Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":8,"time_utc":"2026-08-07T16:30:03Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":9,"time_utc":"2026-09-03T23:05:03Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":10,"time_utc":"2026-10-01T04:18:41Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":10,"time_utc":"2026-10-28T10:24:46Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":11,"time_utc":"2026-11-24T18:39:41Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":12,"time_utc":"2026-12-22T04:19:54Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":1,"time_utc":"2026-01-23T15:49:28Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":2,"time_utc":"2026-02-19T23:30:09Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":3,"time_utc":"2026-03-19T09:34:27Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":4,"time_utc":"2026-04-15T21:23:14Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":5,"time_utc":"2026-05-13T09:11:35Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":6,"time_utc":"2026-06-09T19:13:52Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":7,"time_utc":"2026-07-07T02:37:58Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":8,"time_utc":"2026-08-03T07:56:14Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":8,"time_utc":"2026-08-30T12:50:11Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":9,"time_utc":"2026-09-26T19:04:28Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":10,"time_utc":"2026-10-24T03:16:37Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":11,"time_utc":"2026-11-20T12:35:40Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":12,"time_utc":"2026-12-17T21:26:40Z"}
|
||||||
|
]
|
||||||
|
}
|
||||||
92
basic/testdata/moon_planet_conjunction_baseline_samples.json
vendored
Normal file
92
basic/testdata/moon_planet_conjunction_baseline_samples.json
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"samples": [
|
||||||
|
{"planet":"mercury","year":1996,"month":1,"time_utc":"1996-01-20T07:48:44Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":2,"time_utc":"1996-02-17T05:58:27Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":3,"time_utc":"1996-03-18T21:54:14Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":4,"time_utc":"1996-04-19T10:29:52Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":5,"time_utc":"1996-05-17T04:02:30Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":6,"time_utc":"1996-06-14T00:19:49Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":7,"time_utc":"1996-07-16T08:03:39Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":8,"time_utc":"1996-08-16T18:24:11Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":9,"time_utc":"1996-09-13T13:17:46Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":10,"time_utc":"1996-10-11T09:44:59Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":11,"time_utc":"1996-11-11T12:53:54Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":12,"time_utc":"1996-12-12T05:10:41Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":1,"time_utc":"1996-01-23T08:25:31Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":2,"time_utc":"1996-02-22T04:36:35Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":3,"time_utc":"1996-03-22T23:53:11Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":4,"time_utc":"1996-04-21T14:13:39Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":5,"time_utc":"1996-05-20T00:40:14Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":6,"time_utc":"1996-06-15T09:13:17Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":7,"time_utc":"1996-07-12T08:38:52Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":8,"time_utc":"1996-08-10T03:59:30Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":9,"time_utc":"1996-09-08T23:10:01Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":10,"time_utc":"1996-10-09T04:07:49Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":11,"time_utc":"1996-11-08T09:31:53Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":12,"time_utc":"1996-12-08T13:14:51Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":1,"time_utc":"1996-01-21T07:43:41Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":2,"time_utc":"1996-02-19T07:57:56Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":3,"time_utc":"1996-03-19T07:08:23Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":4,"time_utc":"1996-04-17T05:16:03Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":5,"time_utc":"1996-05-16T02:56:01Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":6,"time_utc":"1996-06-14T00:47:52Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":7,"time_utc":"1996-07-12T23:06:35Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":8,"time_utc":"1996-08-10T21:29:02Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":9,"time_utc":"1996-09-08T19:05:05Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":10,"time_utc":"1996-10-07T14:58:13Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":11,"time_utc":"1996-11-05T08:08:58Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":12,"time_utc":"1996-12-03T21:11:34Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":1,"time_utc":"1996-01-18T19:46:08Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":2,"time_utc":"1996-02-15T15:00:36Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":3,"time_utc":"1996-03-14T06:11:13Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":4,"time_utc":"1996-04-10T16:55:53Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":5,"time_utc":"1996-05-08T00:11:28Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":6,"time_utc":"1996-06-04T05:31:30Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":7,"time_utc":"1996-07-01T10:21:43Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":7,"time_utc":"1996-07-28T15:41:14Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":8,"time_utc":"1996-08-24T22:04:24Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":9,"time_utc":"1996-09-21T05:58:04Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":10,"time_utc":"1996-10-18T16:05:44Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":11,"time_utc":"1996-11-15T05:27:06Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":12,"time_utc":"1996-12-12T22:38:11Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":1,"time_utc":"1996-01-24T03:47:15Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":2,"time_utc":"1996-02-20T19:10:42Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":3,"time_utc":"1996-03-19T10:59:03Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":4,"time_utc":"1996-04-16T01:04:52Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":5,"time_utc":"1996-05-13T12:31:53Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":6,"time_utc":"1996-06-09T21:39:17Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":7,"time_utc":"1996-07-07T05:32:21Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":8,"time_utc":"1996-08-03T13:13:36Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":8,"time_utc":"1996-08-30T21:01:02Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":9,"time_utc":"1996-09-27T04:20:31Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":10,"time_utc":"1996-10-24T10:21:42Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":11,"time_utc":"1996-11-20T15:05:45Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":12,"time_utc":"1996-12-17T20:17:06Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":1,"time_utc":"1996-01-20T15:54:24Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":2,"time_utc":"1996-02-17T05:20:18Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":3,"time_utc":"1996-03-15T16:05:29Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":4,"time_utc":"1996-04-11T23:42:31Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":5,"time_utc":"1996-05-09T05:36:22Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":6,"time_utc":"1996-06-05T11:50:19Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":7,"time_utc":"1996-07-02T19:33:47Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":7,"time_utc":"1996-07-30T04:29:08Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":8,"time_utc":"1996-08-26T13:21:06Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":9,"time_utc":"1996-09-22T20:54:56Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":10,"time_utc":"1996-10-20T03:02:32Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":11,"time_utc":"1996-11-16T09:17:15Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":12,"time_utc":"1996-12-13T17:54:51Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":1,"time_utc":"1996-01-20T07:21:06Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":2,"time_utc":"1996-02-16T19:38:46Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":3,"time_utc":"1996-03-15T05:08:50Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":4,"time_utc":"1996-04-11T11:48:33Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":5,"time_utc":"1996-05-08T17:24:20Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":6,"time_utc":"1996-06-04T23:59:10Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":7,"time_utc":"1996-07-02T08:22:46Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":7,"time_utc":"1996-07-29T17:55:53Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":8,"time_utc":"1996-08-26T03:10:20Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":9,"time_utc":"1996-09-22T10:48:58Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":10,"time_utc":"1996-10-19T16:49:59Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":11,"time_utc":"1996-11-15T22:54:41Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":12,"time_utc":"1996-12-13T07:18:13Z"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"b612.me/astro/jupiter"
|
"b612.me/astro/jupiter"
|
||||||
"b612.me/astro/mars"
|
"b612.me/astro/mars"
|
||||||
"b612.me/astro/mercury"
|
"b612.me/astro/mercury"
|
||||||
|
"b612.me/astro/moon"
|
||||||
"b612.me/astro/neptune"
|
"b612.me/astro/neptune"
|
||||||
"b612.me/astro/saturn"
|
"b612.me/astro/saturn"
|
||||||
"b612.me/astro/uranus"
|
"b612.me/astro/uranus"
|
||||||
@ -88,6 +89,56 @@ func TestPublicPlanetEventBoundaryIncludesCurrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublicMoonPlanetConjunctionBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
type eventFuncs struct {
|
||||||
|
last func(time.Time) time.Time
|
||||||
|
next func(time.Time) time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
eventUT float64
|
||||||
|
funcs eventFuncs
|
||||||
|
}{
|
||||||
|
{name: "MoonMercuryConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionMercury), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionMercury) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionMercury) },
|
||||||
|
}},
|
||||||
|
{name: "MoonVenusConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionVenus), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionVenus) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionVenus) },
|
||||||
|
}},
|
||||||
|
{name: "MoonMarsConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionMars), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionMars) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionMars) },
|
||||||
|
}},
|
||||||
|
{name: "MoonJupiterConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionJupiter), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionJupiter) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionJupiter) },
|
||||||
|
}},
|
||||||
|
{name: "MoonSaturnConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionSaturn), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionSaturn) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionSaturn) },
|
||||||
|
}},
|
||||||
|
{name: "MoonUranusConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionUranus), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionUranus) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionUranus) },
|
||||||
|
}},
|
||||||
|
{name: "MoonNeptuneConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionNeptune), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionNeptune) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionNeptune) },
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
eventTime := basic.JDE2DateByZone(tc.eventUT, time.UTC, false)
|
||||||
|
assertSameEventTime(t, "last", tc.funcs.last(eventTime), eventTime)
|
||||||
|
assertSameEventTime(t, "next", tc.funcs.next(eventTime), eventTime)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func eventBoundaryTT(year int) float64 {
|
func eventBoundaryTT(year int) float64 {
|
||||||
return basic.TD2UT(basic.Date2JDE(time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)), true)
|
return basic.TD2UT(basic.Date2JDE(time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)), true)
|
||||||
}
|
}
|
||||||
|
|||||||
72
moon/conjunction.go
Normal file
72
moon/conjunction.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package moon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction.
|
||||||
|
type ConjunctionPlanet string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConjunctionMercury ConjunctionPlanet = "mercury"
|
||||||
|
ConjunctionVenus ConjunctionPlanet = "venus"
|
||||||
|
ConjunctionMars ConjunctionPlanet = "mars"
|
||||||
|
ConjunctionJupiter ConjunctionPlanet = "jupiter"
|
||||||
|
ConjunctionSaturn ConjunctionPlanet = "saturn"
|
||||||
|
ConjunctionUranus ConjunctionPlanet = "uranus"
|
||||||
|
ConjunctionNeptune ConjunctionPlanet = "neptune"
|
||||||
|
)
|
||||||
|
|
||||||
|
func conjunctionPlanetToBasic(planet ConjunctionPlanet) basic.MoonPlanetConjunctionPlanet {
|
||||||
|
switch planet {
|
||||||
|
case ConjunctionMercury:
|
||||||
|
return basic.MoonPlanetConjunctionMercury
|
||||||
|
case ConjunctionVenus:
|
||||||
|
return basic.MoonPlanetConjunctionVenus
|
||||||
|
case ConjunctionMars:
|
||||||
|
return basic.MoonPlanetConjunctionMars
|
||||||
|
case ConjunctionJupiter:
|
||||||
|
return basic.MoonPlanetConjunctionJupiter
|
||||||
|
case ConjunctionSaturn:
|
||||||
|
return basic.MoonPlanetConjunctionSaturn
|
||||||
|
case ConjunctionUranus:
|
||||||
|
return basic.MoonPlanetConjunctionUranus
|
||||||
|
case ConjunctionNeptune:
|
||||||
|
return basic.MoonPlanetConjunctionNeptune
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validConjunctionPlanet(planet ConjunctionPlanet) bool {
|
||||||
|
return conjunctionPlanetToBasic(planet) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastConjunctionWithPlanet 上一次行星合月(赤经合) / previous Moon-planet conjunction.
|
||||||
|
func LastConjunctionWithPlanet(date time.Time, planet ConjunctionPlanet) time.Time {
|
||||||
|
if !validConjunctionPlanet(planet) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
|
return basic.JDE2DateByZone(basic.LastMoonPlanetConjunction(jde, conjunctionPlanetToBasic(planet)), date.Location(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextConjunctionWithPlanet 下一次行星合月(赤经合) / next Moon-planet conjunction.
|
||||||
|
func NextConjunctionWithPlanet(date time.Time, planet ConjunctionPlanet) time.Time {
|
||||||
|
if !validConjunctionPlanet(planet) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
|
return basic.JDE2DateByZone(basic.NextMoonPlanetConjunction(jde, conjunctionPlanetToBasic(planet)), date.Location(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestConjunctionWithPlanet 最近一次行星合月(赤经合) / closest Moon-planet conjunction.
|
||||||
|
func ClosestConjunctionWithPlanet(date time.Time, planet ConjunctionPlanet) time.Time {
|
||||||
|
if !validConjunctionPlanet(planet) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
|
return basic.JDE2DateByZone(basic.ClosestMoonPlanetConjunction(jde, conjunctionPlanetToBasic(planet)), date.Location(), false)
|
||||||
|
}
|
||||||
82
moon/conjunction_test.go
Normal file
82
moon/conjunction_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package moon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConjunctionPlanetWrappersMatchBasic(t *testing.T) {
|
||||||
|
loc := time.FixedZone("CST", 8*3600)
|
||||||
|
query := time.Date(2026, 1, 15, 20, 0, 0, 0, loc)
|
||||||
|
queryTT := basic.TD2UT(basic.Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
planet ConjunctionPlanet
|
||||||
|
basic basic.MoonPlanetConjunctionPlanet
|
||||||
|
}{
|
||||||
|
{name: "Mercury", planet: ConjunctionMercury, basic: basic.MoonPlanetConjunctionMercury},
|
||||||
|
{name: "Venus", planet: ConjunctionVenus, basic: basic.MoonPlanetConjunctionVenus},
|
||||||
|
{name: "Mars", planet: ConjunctionMars, basic: basic.MoonPlanetConjunctionMars},
|
||||||
|
{name: "Jupiter", planet: ConjunctionJupiter, basic: basic.MoonPlanetConjunctionJupiter},
|
||||||
|
{name: "Saturn", planet: ConjunctionSaturn, basic: basic.MoonPlanetConjunctionSaturn},
|
||||||
|
{name: "Uranus", planet: ConjunctionUranus, basic: basic.MoonPlanetConjunctionUranus},
|
||||||
|
{name: "Neptune", planet: ConjunctionNeptune, basic: basic.MoonPlanetConjunctionNeptune},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assertSameConjunctionTime(t, "last", LastConjunctionWithPlanet(query, tc.planet), basic.LastMoonPlanetConjunction(queryTT, tc.basic), loc)
|
||||||
|
assertSameConjunctionTime(t, "next", NextConjunctionWithPlanet(query, tc.planet), basic.NextMoonPlanetConjunction(queryTT, tc.basic), loc)
|
||||||
|
assertSameConjunctionTime(t, "closest", ClosestConjunctionWithPlanet(query, tc.planet), basic.ClosestMoonPlanetConjunction(queryTT, tc.basic), loc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSameConjunctionTime(t *testing.T, name string, got time.Time, wantJDE float64, loc *time.Location) {
|
||||||
|
t.Helper()
|
||||||
|
want := basic.JDE2DateByZone(wantJDE, loc, false)
|
||||||
|
if got.Location() != loc {
|
||||||
|
t.Fatalf("%s location mismatch: got %q want %q", name, got.Location().String(), loc.String())
|
||||||
|
}
|
||||||
|
if !got.Equal(want) {
|
||||||
|
t.Fatalf("%s time mismatch: got %s want %s", name, got.Format(time.RFC3339Nano), want.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClosestConjunctionReturnsNearestCandidate(t *testing.T) {
|
||||||
|
query := time.Date(2026, 1, 15, 12, 0, 0, 0, time.UTC)
|
||||||
|
last := LastConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
next := NextConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
got := ClosestConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
|
||||||
|
lastDiff := math.Abs(query.Sub(last).Seconds())
|
||||||
|
nextDiff := math.Abs(next.Sub(query).Seconds())
|
||||||
|
if lastDiff <= nextDiff {
|
||||||
|
if !got.Equal(last) {
|
||||||
|
t.Fatalf("closest should match last: got %s want %s", got.Format(time.RFC3339Nano), last.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !got.Equal(next) {
|
||||||
|
t.Fatalf("closest should match next: got %s want %s", got.Format(time.RFC3339Nano), next.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidConjunctionPlanetReturnsZeroTime(t *testing.T) {
|
||||||
|
query := time.Date(2026, 1, 15, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||||
|
invalid := ConjunctionPlanet("pluto")
|
||||||
|
|
||||||
|
for name, fn := range map[string]func(time.Time, ConjunctionPlanet) time.Time{
|
||||||
|
"last": LastConjunctionWithPlanet,
|
||||||
|
"next": NextConjunctionWithPlanet,
|
||||||
|
"closest": ClosestConjunctionWithPlanet,
|
||||||
|
} {
|
||||||
|
if got := fn(query, invalid); !got.IsZero() {
|
||||||
|
t.Fatalf("%s should return zero time for invalid planet, got %s", name, got.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
moon/geocentric_apparent_test.go
Normal file
43
moon/geocentric_apparent_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package moon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeocentricApparentRaDecComponentsMatch(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
ra, dec := GeocentricApparentRaDec(date)
|
||||||
|
if diff := math.Abs(ra - GeocentricApparentRa(date)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, GeocentricApparentRa(date))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - GeocentricApparentDec(date)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, GeocentricApparentDec(date))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeocentricApparentRaDecDiffersFromTopocentricAtSite(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 1, 6, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||||
|
|
||||||
|
geoRA, geoDec := GeocentricApparentRaDec(date)
|
||||||
|
topoRA, topoDec := ApparentRaDec(date, 121.4737, 31.2304)
|
||||||
|
|
||||||
|
if math.Abs(geoRA-topoRA) < 1e-6 && math.Abs(geoDec-topoDec) < 1e-6 {
|
||||||
|
t.Fatalf("geocentric apparent RA/Dec unexpectedly matches topocentric values: geo=(%.12f, %.12f) topo=(%.12f, %.12f)",
|
||||||
|
geoRA, geoDec, topoRA, topoDec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrueRaDecUsesBasicGeocentricTrue(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
wantRA, wantDec := basic.HMoonGeocentricTrueRaDec(basic.TD2UT(basic.Date2JDE(date.UTC()), true))
|
||||||
|
gotRA, gotDec := TrueRaDec(date)
|
||||||
|
if math.Abs(gotRA-wantRA) > 1e-12 || math.Abs(gotDec-wantDec) > 1e-12 {
|
||||||
|
t.Fatalf("TrueRaDec mismatch: got (%.15f, %.15f) want (%.15f, %.15f)", gotRA, gotDec, wantRA, wantDec)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
moon/moon.go
33
moon/moon.go
@ -84,7 +84,7 @@ func ApparentLo(date time.Time) float64 {
|
|||||||
// Returns the Moon's geocentric true right ascension at the instant represented by date, in degrees.
|
// Returns the Moon's geocentric true right ascension at the instant represented by date, in degrees.
|
||||||
func TrueRa(date time.Time) float64 {
|
func TrueRa(date time.Time) float64 {
|
||||||
jde := basic.Date2JDE(date.UTC())
|
jde := basic.Date2JDE(date.UTC())
|
||||||
return basic.HMoonTrueRa(basic.TD2UT(jde, true))
|
return basic.HMoonGeocentricTrueRa(basic.TD2UT(jde, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrueDec 月亮地心真赤纬 / true geocentric declination.
|
// TrueDec 月亮地心真赤纬 / true geocentric declination.
|
||||||
@ -93,7 +93,7 @@ func TrueRa(date time.Time) float64 {
|
|||||||
// Returns the Moon's geocentric true declination at the instant represented by date, in degrees.
|
// Returns the Moon's geocentric true declination at the instant represented by date, in degrees.
|
||||||
func TrueDec(date time.Time) float64 {
|
func TrueDec(date time.Time) float64 {
|
||||||
jde := basic.Date2JDE(date.UTC())
|
jde := basic.Date2JDE(date.UTC())
|
||||||
return basic.HMoonTrueDec(basic.TD2UT(jde, true))
|
return basic.HMoonGeocentricTrueDec(basic.TD2UT(jde, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrueRaDec 月亮地心真赤经、真赤纬 / true geocentric right ascension and declination.
|
// TrueRaDec 月亮地心真赤经、真赤纬 / true geocentric right ascension and declination.
|
||||||
@ -102,7 +102,34 @@ func TrueDec(date time.Time) float64 {
|
|||||||
// Returns the Moon's geocentric true right ascension and declination at the instant represented by date, in degrees.
|
// Returns the Moon's geocentric true right ascension and declination at the instant represented by date, in degrees.
|
||||||
func TrueRaDec(date time.Time) (float64, float64) {
|
func TrueRaDec(date time.Time) (float64, float64) {
|
||||||
jde := basic.Date2JDE(date.UTC())
|
jde := basic.Date2JDE(date.UTC())
|
||||||
return basic.HMoonTrueRaDec(basic.TD2UT(jde, true))
|
return basic.HMoonGeocentricTrueRaDec(basic.TD2UT(jde, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeocentricApparentRa 月亮地心视赤经 / apparent geocentric right ascension.
|
||||||
|
//
|
||||||
|
// 返回月亮在 date 对应绝对时刻的地心视赤经,单位度。
|
||||||
|
// Returns the Moon's apparent geocentric right ascension at the instant represented by date, in degrees.
|
||||||
|
func GeocentricApparentRa(date time.Time) float64 {
|
||||||
|
jde := basic.Date2JDE(date.UTC())
|
||||||
|
return basic.HMoonGeocentricApparentRa(basic.TD2UT(jde, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeocentricApparentDec 月亮地心视赤纬 / apparent geocentric declination.
|
||||||
|
//
|
||||||
|
// 返回月亮在 date 对应绝对时刻的地心视赤纬,单位度。
|
||||||
|
// Returns the Moon's apparent geocentric declination at the instant represented by date, in degrees.
|
||||||
|
func GeocentricApparentDec(date time.Time) float64 {
|
||||||
|
jde := basic.Date2JDE(date.UTC())
|
||||||
|
return basic.HMoonGeocentricApparentDec(basic.TD2UT(jde, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeocentricApparentRaDec 月亮地心视赤经、视赤纬 / apparent geocentric right ascension and declination.
|
||||||
|
//
|
||||||
|
// 返回月亮在 date 对应绝对时刻的地心视赤经与视赤纬,单位度。
|
||||||
|
// Returns the Moon's apparent geocentric right ascension and declination at the instant represented by date, in degrees.
|
||||||
|
func GeocentricApparentRaDec(date time.Time) (float64, float64) {
|
||||||
|
jde := basic.Date2JDE(date.UTC())
|
||||||
|
return basic.HMoonGeocentricApparentRaDec(basic.TD2UT(jde, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApparentRa 月亮站心视赤经 / apparent topocentric right ascension.
|
// ApparentRa 月亮站心视赤经 / apparent topocentric right ascension.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user