499 lines
16 KiB
Go
499 lines
16 KiB
Go
|
|
package basic
|
||
|
|
|
||
|
|
import "math"
|
||
|
|
|
||
|
|
const (
|
||
|
|
jupiterGalileanEventSearchSpanDays = 8 * 365.25
|
||
|
|
jupiterGalileanEventEpsilonDays = 1.0 / 86400.0
|
||
|
|
jupiterGalileanBoundaryStepDays = 1.0 / 24.0
|
||
|
|
)
|
||
|
|
|
||
|
|
// JupiterGalileanPhenomenonType 伽利略卫星现象类型 / Galilean-satellite phenomenon type.
|
||
|
|
type JupiterGalileanPhenomenonType string
|
||
|
|
|
||
|
|
const (
|
||
|
|
// JupiterGalileanTransit 凌日 / satellite transit across Jupiter.
|
||
|
|
JupiterGalileanTransit JupiterGalileanPhenomenonType = "transit"
|
||
|
|
// JupiterGalileanOccultation 掩蔽 / occultation behind Jupiter.
|
||
|
|
JupiterGalileanOccultation JupiterGalileanPhenomenonType = "occultation"
|
||
|
|
// JupiterGalileanEclipse 食 / eclipse in Jupiter's shadow.
|
||
|
|
JupiterGalileanEclipse JupiterGalileanPhenomenonType = "eclipse"
|
||
|
|
// JupiterGalileanShadowTransit 影凌 / shadow transit across Jupiter.
|
||
|
|
JupiterGalileanShadowTransit JupiterGalileanPhenomenonType = "shadow_transit"
|
||
|
|
)
|
||
|
|
|
||
|
|
// JupiterGalileanPhenomenonEvent 伽利略卫星现象整场事件 / full Galilean-satellite phenomenon event.
|
||
|
|
//
|
||
|
|
// Start、Greatest、End 都使用 UTC/UT 对应的儒略日。
|
||
|
|
// Start, Greatest, and End are UTC/UT Julian days.
|
||
|
|
type JupiterGalileanPhenomenonEvent struct {
|
||
|
|
Valid bool
|
||
|
|
Satellite int
|
||
|
|
Type JupiterGalileanPhenomenonType
|
||
|
|
|
||
|
|
Start float64
|
||
|
|
Greatest float64
|
||
|
|
End float64
|
||
|
|
|
||
|
|
GreatestPhenomenon JupiterGalileanPhenomenon
|
||
|
|
}
|
||
|
|
|
||
|
|
type jupiterGalileanMetricSample struct {
|
||
|
|
active bool
|
||
|
|
metric float64
|
||
|
|
phenomenon JupiterGalileanPhenomenon
|
||
|
|
}
|
||
|
|
|
||
|
|
type jupiterGalileanShadowPoint struct {
|
||
|
|
hasIntersection bool
|
||
|
|
visible bool
|
||
|
|
pathLengthAU float64
|
||
|
|
xAU float64
|
||
|
|
yAU float64
|
||
|
|
xJupiterRadii float64
|
||
|
|
yJupiterRadii float64
|
||
|
|
}
|
||
|
|
|
||
|
|
// LastJupiterGalileanPhenomenonEvent 上一次伽利略卫星现象 / previous Galilean-satellite event.
|
||
|
|
func LastJupiterGalileanPhenomenonEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||
|
|
event, _ := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, -1, true)
|
||
|
|
return event
|
||
|
|
}
|
||
|
|
|
||
|
|
// NextJupiterGalileanPhenomenonEvent 下一次伽利略卫星现象 / next Galilean-satellite event.
|
||
|
|
func NextJupiterGalileanPhenomenonEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||
|
|
event, _ := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, 1, false)
|
||
|
|
return event
|
||
|
|
}
|
||
|
|
|
||
|
|
// ClosestJupiterGalileanPhenomenonEvent 最近一次伽利略卫星现象 / closest Galilean-satellite event.
|
||
|
|
func ClosestJupiterGalileanPhenomenonEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||
|
|
last, hasLast := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, -1, true)
|
||
|
|
next, hasNext := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, 1, false)
|
||
|
|
switch {
|
||
|
|
case hasLast && !hasNext:
|
||
|
|
return last
|
||
|
|
case !hasLast && hasNext:
|
||
|
|
return next
|
||
|
|
case !hasLast && !hasNext:
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent()
|
||
|
|
}
|
||
|
|
if math.Abs(last.Greatest-jd) <= math.Abs(next.Greatest-jd) {
|
||
|
|
return last
|
||
|
|
}
|
||
|
|
return next
|
||
|
|
}
|
||
|
|
|
||
|
|
func searchJupiterGalileanPhenomenonEvent(
|
||
|
|
jd float64,
|
||
|
|
satellite int,
|
||
|
|
phenomenonType JupiterGalileanPhenomenonType,
|
||
|
|
direction int,
|
||
|
|
includeCurrent bool,
|
||
|
|
) (JupiterGalileanPhenomenonEvent, bool) {
|
||
|
|
if !isFinite(jd) || direction == 0 || satellite < 1 || satellite > 4 || !isValidJupiterGalileanPhenomenonType(phenomenonType) {
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent(), false
|
||
|
|
}
|
||
|
|
|
||
|
|
if sample := jupiterGalileanPhenomenonMetricAt(jd, satellite, phenomenonType); sample.active {
|
||
|
|
current := findJupiterGalileanPhenomenonEventAround(jd, satellite, phenomenonType)
|
||
|
|
if current.Valid && includeCurrent {
|
||
|
|
return current, true
|
||
|
|
}
|
||
|
|
if current.Valid {
|
||
|
|
if direction > 0 {
|
||
|
|
jd = current.End + jupiterGalileanEventEpsilonDays
|
||
|
|
} else {
|
||
|
|
jd = current.Start - jupiterGalileanEventEpsilonDays
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
stepDays := jupiterGalileanCoarseStepDays(satellite)
|
||
|
|
maxSteps := int(math.Ceil(jupiterGalileanEventSearchSpanDays / stepDays))
|
||
|
|
sign := float64(direction)
|
||
|
|
|
||
|
|
prevTime := jd
|
||
|
|
prevSample := jupiterGalileanPhenomenonMetricAt(prevTime, satellite, phenomenonType)
|
||
|
|
midTime := jd + sign*stepDays
|
||
|
|
midSample := jupiterGalileanPhenomenonMetricAt(midTime, satellite, phenomenonType)
|
||
|
|
|
||
|
|
for i := 2; i <= maxSteps; i++ {
|
||
|
|
nextTime := jd + sign*float64(i)*stepDays
|
||
|
|
nextSample := jupiterGalileanPhenomenonMetricAt(nextTime, satellite, phenomenonType)
|
||
|
|
if isFinite(midSample.metric) &&
|
||
|
|
midSample.metric <= prevSample.metric &&
|
||
|
|
midSample.metric <= nextSample.metric {
|
||
|
|
candidate := refineJupiterGalileanMetricMinimum(prevTime, nextTime, satellite, phenomenonType)
|
||
|
|
event := findJupiterGalileanPhenomenonEventAround(candidate, satellite, phenomenonType)
|
||
|
|
if event.Valid && jupiterGalileanEventMatchesDirection(event.Greatest, jd, direction, includeCurrent) {
|
||
|
|
return event, true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
prevTime, prevSample = midTime, midSample
|
||
|
|
midTime, midSample = nextTime, nextSample
|
||
|
|
}
|
||
|
|
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent(), false
|
||
|
|
}
|
||
|
|
|
||
|
|
func findJupiterGalileanPhenomenonEventAround(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||
|
|
sample := jupiterGalileanPhenomenonMetricAt(jd, satellite, phenomenonType)
|
||
|
|
if !sample.active {
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent()
|
||
|
|
}
|
||
|
|
|
||
|
|
stepDays := jupiterGalileanBoundaryStep(satellite)
|
||
|
|
maxBoundarySteps := int(math.Ceil(jupiterGalileanOrbitPeriodDays(satellite)/stepDays)) + 4
|
||
|
|
|
||
|
|
activeStart := jd
|
||
|
|
inactiveStart := math.NaN()
|
||
|
|
for i := 0; i < maxBoundarySteps; i++ {
|
||
|
|
candidate := activeStart - stepDays
|
||
|
|
if !jupiterGalileanPhenomenonMetricAt(candidate, satellite, phenomenonType).active {
|
||
|
|
inactiveStart = candidate
|
||
|
|
break
|
||
|
|
}
|
||
|
|
activeStart = candidate
|
||
|
|
}
|
||
|
|
if !isFinite(inactiveStart) {
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent()
|
||
|
|
}
|
||
|
|
|
||
|
|
activeEnd := jd
|
||
|
|
inactiveEnd := math.NaN()
|
||
|
|
for i := 0; i < maxBoundarySteps; i++ {
|
||
|
|
candidate := activeEnd + stepDays
|
||
|
|
if !jupiterGalileanPhenomenonMetricAt(candidate, satellite, phenomenonType).active {
|
||
|
|
inactiveEnd = candidate
|
||
|
|
break
|
||
|
|
}
|
||
|
|
activeEnd = candidate
|
||
|
|
}
|
||
|
|
if !isFinite(inactiveEnd) {
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent()
|
||
|
|
}
|
||
|
|
|
||
|
|
start := refineJupiterGalileanEventStart(inactiveStart, activeStart, satellite, phenomenonType)
|
||
|
|
end := refineJupiterGalileanEventEnd(activeEnd, inactiveEnd, satellite, phenomenonType)
|
||
|
|
if !isFinite(start) || !isFinite(end) || end <= start {
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent()
|
||
|
|
}
|
||
|
|
|
||
|
|
greatest := refineJupiterGalileanMetricMinimum(start, end, satellite, phenomenonType)
|
||
|
|
greatestSample := jupiterGalileanPhenomenonMetricAt(greatest, satellite, phenomenonType)
|
||
|
|
if !greatestSample.active {
|
||
|
|
return invalidJupiterGalileanPhenomenonEvent()
|
||
|
|
}
|
||
|
|
|
||
|
|
return JupiterGalileanPhenomenonEvent{
|
||
|
|
Valid: true,
|
||
|
|
Satellite: satellite,
|
||
|
|
Type: phenomenonType,
|
||
|
|
Start: start,
|
||
|
|
Greatest: greatest,
|
||
|
|
End: end,
|
||
|
|
GreatestPhenomenon: greatestSample.phenomenon,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func refineJupiterGalileanEventStart(
|
||
|
|
outsideJD, insideJD float64,
|
||
|
|
satellite int,
|
||
|
|
phenomenonType JupiterGalileanPhenomenonType,
|
||
|
|
) float64 {
|
||
|
|
if insideJD < outsideJD {
|
||
|
|
outsideJD, insideJD = insideJD, outsideJD
|
||
|
|
}
|
||
|
|
if jupiterGalileanPhenomenonMetricAt(outsideJD, satellite, phenomenonType).active {
|
||
|
|
return math.NaN()
|
||
|
|
}
|
||
|
|
if !jupiterGalileanPhenomenonMetricAt(insideJD, satellite, phenomenonType).active {
|
||
|
|
return math.NaN()
|
||
|
|
}
|
||
|
|
left := outsideJD
|
||
|
|
right := insideJD
|
||
|
|
for i := 0; i < 80 && right-left > jupiterGalileanEventEpsilonDays; i++ {
|
||
|
|
mid := (left + right) / 2
|
||
|
|
if jupiterGalileanPhenomenonMetricAt(mid, satellite, phenomenonType).active {
|
||
|
|
right = mid
|
||
|
|
} else {
|
||
|
|
left = mid
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return right
|
||
|
|
}
|
||
|
|
|
||
|
|
func refineJupiterGalileanEventEnd(
|
||
|
|
insideJD, outsideJD float64,
|
||
|
|
satellite int,
|
||
|
|
phenomenonType JupiterGalileanPhenomenonType,
|
||
|
|
) float64 {
|
||
|
|
if outsideJD < insideJD {
|
||
|
|
insideJD, outsideJD = outsideJD, insideJD
|
||
|
|
}
|
||
|
|
if !jupiterGalileanPhenomenonMetricAt(insideJD, satellite, phenomenonType).active {
|
||
|
|
return math.NaN()
|
||
|
|
}
|
||
|
|
if jupiterGalileanPhenomenonMetricAt(outsideJD, satellite, phenomenonType).active {
|
||
|
|
return math.NaN()
|
||
|
|
}
|
||
|
|
left := insideJD
|
||
|
|
right := outsideJD
|
||
|
|
for i := 0; i < 80 && right-left > jupiterGalileanEventEpsilonDays; i++ {
|
||
|
|
mid := (left + right) / 2
|
||
|
|
if jupiterGalileanPhenomenonMetricAt(mid, satellite, phenomenonType).active {
|
||
|
|
left = mid
|
||
|
|
} else {
|
||
|
|
right = mid
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return left
|
||
|
|
}
|
||
|
|
|
||
|
|
func refineJupiterGalileanMetricMinimum(
|
||
|
|
jd1, jd2 float64,
|
||
|
|
satellite int,
|
||
|
|
phenomenonType JupiterGalileanPhenomenonType,
|
||
|
|
) float64 {
|
||
|
|
left := math.Min(jd1, jd2)
|
||
|
|
right := math.Max(jd1, jd2)
|
||
|
|
if right-left <= jupiterGalileanEventEpsilonDays {
|
||
|
|
return (left + right) / 2
|
||
|
|
}
|
||
|
|
const phi = 0.6180339887498948482
|
||
|
|
x1 := right - phi*(right-left)
|
||
|
|
x2 := left + phi*(right-left)
|
||
|
|
f1 := jupiterGalileanPhenomenonMetricAt(x1, satellite, phenomenonType).metric
|
||
|
|
f2 := jupiterGalileanPhenomenonMetricAt(x2, satellite, phenomenonType).metric
|
||
|
|
for i := 0; i < 80 && right-left > jupiterGalileanEventEpsilonDays; i++ {
|
||
|
|
if f1 <= f2 {
|
||
|
|
right = x2
|
||
|
|
x2 = x1
|
||
|
|
f2 = f1
|
||
|
|
x1 = right - phi*(right-left)
|
||
|
|
f1 = jupiterGalileanPhenomenonMetricAt(x1, satellite, phenomenonType).metric
|
||
|
|
} else {
|
||
|
|
left = x1
|
||
|
|
x1 = x2
|
||
|
|
f1 = f2
|
||
|
|
x2 = left + phi*(right-left)
|
||
|
|
f2 = jupiterGalileanPhenomenonMetricAt(x2, satellite, phenomenonType).metric
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return (left + right) / 2
|
||
|
|
}
|
||
|
|
|
||
|
|
func jupiterGalileanPhenomenonMetricAt(
|
||
|
|
jd float64,
|
||
|
|
satellite int,
|
||
|
|
phenomenonType JupiterGalileanPhenomenonType,
|
||
|
|
) jupiterGalileanMetricSample {
|
||
|
|
if !isFinite(jd) || satellite < 1 || satellite > 4 || !isValidJupiterGalileanPhenomenonType(phenomenonType) {
|
||
|
|
return jupiterGalileanMetricSample{
|
||
|
|
metric: math.Inf(1),
|
||
|
|
phenomenon: invalidJupiterGalileanPhenomenon(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
evaluationJD := TD2UT(jd, true)
|
||
|
|
context := newJupiterGalileanObservationContext(evaluationJD)
|
||
|
|
if context.jupiterDistance == 0 {
|
||
|
|
return jupiterGalileanMetricSample{
|
||
|
|
metric: math.Inf(1),
|
||
|
|
phenomenon: invalidJupiterGalileanPhenomenon(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
index := satellite - 1
|
||
|
|
observation := context.observationForSatellite(index)
|
||
|
|
stateVector := Vector3{observation.State.X, observation.State.Y, observation.State.Z}
|
||
|
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||
|
|
|
||
|
|
xEarth := observation.OffsetXJupiterRadii
|
||
|
|
yEarth := observation.OffsetYJupiterRadii
|
||
|
|
earthMetric := ellipseMetric(xEarth, yEarth, 1, context.earthMinorRadius)
|
||
|
|
onEarthDisk := ellipseInside(xEarth, yEarth, 1, context.earthMinorRadius)
|
||
|
|
|
||
|
|
xSunAU := vectorDot(stateVector, context.sunEast)
|
||
|
|
ySunAU := vectorDot(stateVector, context.sunNorth)
|
||
|
|
zSunAU := vectorDot(stateVector, context.sunLineOfSight)
|
||
|
|
xSun := xSunAU / radiusAU
|
||
|
|
ySun := ySunAU / radiusAU
|
||
|
|
umbraScale := jupiterUmbraScale(zSunAU, context.sunDistanceAU)
|
||
|
|
sunMetric := math.Inf(1)
|
||
|
|
eclipse := false
|
||
|
|
if umbraScale > 0 {
|
||
|
|
sunMetric = ellipseMetric(xSun, ySun, umbraScale, context.sunMinorRadius*umbraScale)
|
||
|
|
eclipse = zSunAU > 0 && sunMetric <= 1+1e-12
|
||
|
|
}
|
||
|
|
|
||
|
|
shadowPoint := context.shadowPointFor(stateVector)
|
||
|
|
shadowMetric := math.Inf(1)
|
||
|
|
shadowTransit := false
|
||
|
|
if shadowPoint.hasIntersection {
|
||
|
|
shadowMetric = ellipseMetric(shadowPoint.xJupiterRadii, shadowPoint.yJupiterRadii, 1, context.earthMinorRadius)
|
||
|
|
shadowTransit = shadowPoint.visible && shadowMetric <= 1+1e-12
|
||
|
|
}
|
||
|
|
|
||
|
|
phenomenon := JupiterGalileanPhenomenon{
|
||
|
|
Transit: onEarthDisk && observation.InFrontOfJupiter,
|
||
|
|
Occultation: onEarthDisk && !observation.InFrontOfJupiter,
|
||
|
|
Eclipse: eclipse,
|
||
|
|
ShadowTransit: shadowTransit,
|
||
|
|
|
||
|
|
ShadowOffsetXArcsec: math.NaN(),
|
||
|
|
ShadowOffsetYArcsec: math.NaN(),
|
||
|
|
ShadowOffsetXJupiterRadii: math.NaN(),
|
||
|
|
ShadowOffsetYJupiterRadii: math.NaN(),
|
||
|
|
}
|
||
|
|
if shadowTransit {
|
||
|
|
phenomenon.ShadowOffsetXArcsec = math.Atan2(shadowPoint.xAU, context.jupiterDistance) * deg * 3600
|
||
|
|
phenomenon.ShadowOffsetYArcsec = math.Atan2(shadowPoint.yAU, context.jupiterDistance) * deg * 3600
|
||
|
|
phenomenon.ShadowOffsetXJupiterRadii = shadowPoint.xJupiterRadii
|
||
|
|
phenomenon.ShadowOffsetYJupiterRadii = shadowPoint.yJupiterRadii
|
||
|
|
}
|
||
|
|
|
||
|
|
switch phenomenonType {
|
||
|
|
case JupiterGalileanTransit:
|
||
|
|
metric := earthMetric
|
||
|
|
if !observation.InFrontOfJupiter {
|
||
|
|
metric += 4
|
||
|
|
}
|
||
|
|
return jupiterGalileanMetricSample{active: phenomenon.Transit, metric: metric, phenomenon: phenomenon}
|
||
|
|
case JupiterGalileanOccultation:
|
||
|
|
metric := earthMetric
|
||
|
|
if observation.InFrontOfJupiter {
|
||
|
|
metric += 4
|
||
|
|
}
|
||
|
|
return jupiterGalileanMetricSample{active: phenomenon.Occultation, metric: metric, phenomenon: phenomenon}
|
||
|
|
case JupiterGalileanEclipse:
|
||
|
|
metric := sunMetric
|
||
|
|
if zSunAU <= 0 {
|
||
|
|
metric += 4
|
||
|
|
}
|
||
|
|
return jupiterGalileanMetricSample{active: phenomenon.Eclipse, metric: metric, phenomenon: phenomenon}
|
||
|
|
case JupiterGalileanShadowTransit:
|
||
|
|
metric := shadowMetric
|
||
|
|
if shadowPoint.hasIntersection && !shadowPoint.visible {
|
||
|
|
metric += 4
|
||
|
|
}
|
||
|
|
return jupiterGalileanMetricSample{active: phenomenon.ShadowTransit, metric: metric, phenomenon: phenomenon}
|
||
|
|
default:
|
||
|
|
return jupiterGalileanMetricSample{metric: math.Inf(1), phenomenon: invalidJupiterGalileanPhenomenon()}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (context jupiterGalileanObservationContext) shadowPointFor(stateVector Vector3) jupiterGalileanShadowPoint {
|
||
|
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||
|
|
satelliteBody := context.toBodyCoordinates(stateVector)
|
||
|
|
satelliteBody = Vector3{
|
||
|
|
satelliteBody[0] / radiusAU,
|
||
|
|
satelliteBody[1] / radiusAU,
|
||
|
|
satelliteBody[2] / radiusAU,
|
||
|
|
}
|
||
|
|
directionBody := context.toBodyCoordinates(context.sunLineOfSight)
|
||
|
|
intersectionBody, ok := ellipsoidRayIntersection(satelliteBody, directionBody, jupiterPolarRadiusRatio())
|
||
|
|
if !ok {
|
||
|
|
return jupiterGalileanShadowPoint{}
|
||
|
|
}
|
||
|
|
normalBody := Vector3{
|
||
|
|
intersectionBody[0],
|
||
|
|
intersectionBody[1],
|
||
|
|
intersectionBody[2] / (jupiterPolarRadiusRatio() * jupiterPolarRadiusRatio()),
|
||
|
|
}
|
||
|
|
earthBody := context.toBodyCoordinates(context.earthDirection)
|
||
|
|
intersection := context.fromBodyCoordinates(Vector3{
|
||
|
|
intersectionBody[0] * radiusAU,
|
||
|
|
intersectionBody[1] * radiusAU,
|
||
|
|
intersectionBody[2] * radiusAU,
|
||
|
|
})
|
||
|
|
dx := intersection[0] - stateVector[0]
|
||
|
|
dy := intersection[1] - stateVector[1]
|
||
|
|
dz := intersection[2] - stateVector[2]
|
||
|
|
xAU := vectorDot(intersection, context.east)
|
||
|
|
yAU := vectorDot(intersection, context.north)
|
||
|
|
xR := xAU / radiusAU
|
||
|
|
yR := yAU / radiusAU
|
||
|
|
return jupiterGalileanShadowPoint{
|
||
|
|
hasIntersection: true,
|
||
|
|
visible: vectorDot(normalBody, earthBody) > 0,
|
||
|
|
pathLengthAU: math.Sqrt(dx*dx + dy*dy + dz*dz),
|
||
|
|
xAU: xAU,
|
||
|
|
yAU: yAU,
|
||
|
|
xJupiterRadii: xR,
|
||
|
|
yJupiterRadii: yR,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func jupiterGalileanOrbitPeriodDays(satellite int) float64 {
|
||
|
|
switch satellite {
|
||
|
|
case 1:
|
||
|
|
return 1.769137786
|
||
|
|
case 2:
|
||
|
|
return 3.551181
|
||
|
|
case 3:
|
||
|
|
return 7.154553
|
||
|
|
case 4:
|
||
|
|
return 16.689018
|
||
|
|
default:
|
||
|
|
return math.NaN()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func jupiterGalileanCoarseStepDays(satellite int) float64 {
|
||
|
|
step := jupiterGalileanOrbitPeriodDays(satellite) / 16
|
||
|
|
maxStep := 2.0 / 24.0
|
||
|
|
if step > maxStep {
|
||
|
|
return maxStep
|
||
|
|
}
|
||
|
|
return step
|
||
|
|
}
|
||
|
|
|
||
|
|
func jupiterGalileanBoundaryStep(satellite int) float64 {
|
||
|
|
step := jupiterGalileanOrbitPeriodDays(satellite) / 32
|
||
|
|
if step > jupiterGalileanBoundaryStepDays {
|
||
|
|
return jupiterGalileanBoundaryStepDays
|
||
|
|
}
|
||
|
|
return step
|
||
|
|
}
|
||
|
|
|
||
|
|
func jupiterGalileanEventMatchesDirection(eventJD, targetJD float64, direction int, includeCurrent bool) bool {
|
||
|
|
diff := eventJD - targetJD
|
||
|
|
switch {
|
||
|
|
case direction < 0 && includeCurrent:
|
||
|
|
return diff <= jupiterGalileanEventEpsilonDays
|
||
|
|
case direction < 0:
|
||
|
|
return diff < -jupiterGalileanEventEpsilonDays
|
||
|
|
case includeCurrent:
|
||
|
|
return diff >= -jupiterGalileanEventEpsilonDays
|
||
|
|
default:
|
||
|
|
return diff > jupiterGalileanEventEpsilonDays
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func ellipseMetric(x, y, major, minor float64) float64 {
|
||
|
|
if major <= 0 || minor <= 0 {
|
||
|
|
return math.Inf(1)
|
||
|
|
}
|
||
|
|
return (x*x)/(major*major) + (y*y)/(minor*minor)
|
||
|
|
}
|
||
|
|
|
||
|
|
func isValidJupiterGalileanPhenomenonType(phenomenonType JupiterGalileanPhenomenonType) bool {
|
||
|
|
switch phenomenonType {
|
||
|
|
case JupiterGalileanTransit, JupiterGalileanOccultation, JupiterGalileanEclipse, JupiterGalileanShadowTransit:
|
||
|
|
return true
|
||
|
|
default:
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func invalidJupiterGalileanPhenomenonEvent() JupiterGalileanPhenomenonEvent {
|
||
|
|
return JupiterGalileanPhenomenonEvent{
|
||
|
|
Start: math.NaN(),
|
||
|
|
Greatest: math.NaN(),
|
||
|
|
End: math.NaN(),
|
||
|
|
GreatestPhenomenon: invalidJupiterGalileanPhenomenon(),
|
||
|
|
}
|
||
|
|
}
|