// Vertical computes data for a vertical sundial. // // Argument φ is geographic latitude at which the sundial will be located. // D is gnomonic declination, the azimuth of the perpendicular to the plane // of the sundial, measured from the southern meridian towards the west. // Argument a is the length of a straight stylus perpendicular to the plane // of the sundial. // // Results consist of a set of lines, a center point, and u, the length of a // polar stylus. They are in units of a, the stylus length. func Vertical(φ, D unit.Angle, a float64) (lines []Line, center Point, u float64) { sφ, cφ := φ.Sincos() tφ := sφ / cφ sD, cD := D.Sincos() for i := 0; i < 24; i++ { l := Line{Hour: i} H := float64(i-12) * 15 * math.Pi / 180 aH := math.Abs(H) sH, cH := math.Sincos(H) for _, d := range m { tδ := math.Tan(d * math.Pi / 180) H0 := math.Acos(-tφ * tδ) if aH > H0 { continue // sun below horizon } Q := sD*sH + sφ*cD*cH - cφ*cD*tδ if Q < 0 { continue // sun below plane of sundial } x := a * (cD*sH - sφ*sD*cH + cφ*sD*tδ) / Q y := -a * (cφ*cH + sφ*tδ) / Q l.Points = append(l.Points, Point{x, y}) } if len(l.Points) > 0 { lines = append(lines, l) } } center.X = -a * sD / cD center.Y = a * tφ / cD u = a / math.Abs(cφ*cD) return }
// Kepler1 solves Kepler's equation by iteration. // // The iterated formula is // // E1 = M + e * sin(E0) // // Argument e is eccentricity, M is mean anomaly, // places is the desired number of decimal places in the result. // // Result E is eccentric anomaly. // // For some vaues of e and M it will fail to converge and the // function will return an error. func Kepler1(e float64, M unit.Angle, places int) (E unit.Angle, err error) { f := func(E0 float64) float64 { return M.Rad() + e*math.Sin(E0) // (30.5) p. 195 } ea, err := iterate.DecimalPlaces(f, M.Rad(), places, places*5) return unit.Angle(ea), err }
func (m *moon) physical(A, bʹ unit.Angle) (lʺ, bʺ unit.Angle) { // (53.2) p. 373 sA, cA := A.Sincos() lʺ = -m.τ + (m.ρ.Mul(cA) + m.σ.Mul(sA)).Mul(bʹ.Tan()) bʺ = m.σ.Mul(cA) - m.ρ.Mul(sA) return }
// Kepler2 solves Kepler's equation by iteration. // // The iterated formula is // // E1 = E0 + (M + e * sin(E0) - E0) / (1 - e * cos(E0)) // // Argument e is eccentricity, M is mean anomaly, // places is the desired number of decimal places in the result. // // Result E is eccentric anomaly. // // The function converges over a wider range of inputs than does Kepler1 // but it also fails to converge for some values of e and M. func Kepler2(e float64, M unit.Angle, places int) (E unit.Angle, err error) { f := func(E0 float64) float64 { se, ce := math.Sincos(E0) return E0 + (M.Rad()+e*se-E0)/(1-e*ce) // (30.7) p. 199 } ea, err := iterate.DecimalPlaces(f, M.Rad(), places, places) return unit.Angle(ea), err }
// SepPauwels returns the angular separation between two celestial bodies. // // The algorithm is a numerically stable form of that used in Sep. func SepPauwels(r1, d1, r2, d2 unit.Angle) unit.Angle { sd1, cd1 := d1.Sincos() sd2, cd2 := d2.Sincos() cdr := (r2 - r1).Cos() x := cd1*sd2 - sd1*cd2*cdr y := cd2 * (r2 - r1).Sin() z := sd1*sd2 + cd1*cd2*cdr return unit.Angle(math.Atan2(math.Hypot(x, y), z)) }
// GalToEq converts galactic coordinates to equatorial coordinates. // // Resulting equatorial coordinates will be referred to the standard equinox of // B1950.0. For subsequent conversion to other epochs, see package precess and // utility functions in package meeus. func GalToEq(l, b unit.Angle) (α unit.RA, δ unit.Angle) { sdLon, cdLon := (l - galacticLon0).Sincos() sgδ, cgδ := galacticNorth.Dec.Sincos() sb, cb := b.Sincos() y := math.Atan2(sdLon, cdLon*sgδ-(sb/cb)*cgδ) α = unit.RAFromRad(y + galacticNorth.RA.Rad()) δ = unit.Angle(math.Asin(sb*sgδ + cb*cgδ*cdLon)) return }
// HzToEq transforms horizontal coordinates to equatorial coordinates. // // A: azimuth // h: elevation // φ: latitude of observer on Earth // ψ: longitude of observer on Earth // st: sidereal time at Greenwich at time of observation. // // Sidereal time must be consistent with the equatorial coordinates // in the sense that tf coordinates are apparent, sidereal time must be // apparent as well. // // Results: // // α: right ascension // δ: declination func HzToEq(A, h, φ, ψ unit.Angle, st unit.Time) (α unit.RA, δ unit.Angle) { sA, cA := A.Sincos() sh, ch := h.Sincos() sφ, cφ := φ.Sincos() H := math.Atan2(sA, cA*sφ+sh/ch*cφ) α = unit.RAFromRad(st.Rad() - ψ.Rad() - H) δ = unit.Angle(math.Asin(sφ*sh - cφ*ch*cA)) return }
// Kepler2a solves Kepler's equation by iteration. // // The iterated formula is the same as in Kepler2 but a limiting function // avoids divergence. // // Argument e is eccentricity, M is mean anomaly, // places is the desired number of decimal places in the result. // // Result E is eccentric anomaly. func Kepler2a(e float64, M unit.Angle, places int) (E unit.Angle, err error) { f := func(E0 float64) float64 { se, ce := math.Sincos(E0) // method of Leingärtner, p. 205 return E0 + math.Asin(math.Sin((M.Rad()+e*se-E0)/(1-e*ce))) } ea, err := iterate.DecimalPlaces(f, M.Rad(), places, places*5) return unit.Angle(ea), err }
// ApparentEccentricity returns apparent eccenticity of a binary star // given true orbital elements. // // e is eccentricity of the true orbit // i is inclination relative to the line of sight // ω is longitude of periastron func ApparentEccentricity(e float64, i, ω unit.Angle) float64 { ci := i.Cos() sω, cω := ω.Sincos() A := (1 - e*e*cω*cω) * ci * ci B := e * e * sω * cω * ci C := 1 - e*e*sω*sω d := A - C sD := math.Sqrt(d*d + 4*B*B) return math.Sqrt(2 * sD / (A + C + sD)) }
// Sep returns the angular separation between two celestial bodies. // // The algorithm is numerically naïve, and while patched up a bit for // small separations, remains unstable for separations near π. func Sep(r1, d1, r2, d2 unit.Angle) unit.Angle { sd1, cd1 := d1.Sincos() sd2, cd2 := d2.Sincos() cd := sd1*sd2 + cd1*cd2*(r1-r2).Cos() // (17.1) p. 109 if cd < base.CosSmallAngle { return unit.Angle(math.Acos(cd)) } // (17.2) p. 109 return unit.Angle(math.Hypot((r2-r1).Rad()*cd1, (d2 - d1).Rad())) }
// Times computes UT rise, transit and set times for a celestial object on // a day of interest. // // The function argurments do not actually include the day, but do include // a number of values computed from the day. // // p is geographic coordinates of observer. // ΔT is delta T. // h0 is "standard altitude" of the body. // Th0 is apparent sidereal time at 0h UT at Greenwich. // α3, δ3 are slices of three right ascensions and declinations. // // h0 unit is radians. // // Th0 must be the time on the day of interest, in seconds. // See sidereal.Apparent0UT. // // α3, δ3 must be values at 0h dynamical time for the day before, the day of, // and the day after the day of interest. Units are radians. // // Result units are seconds of day and are in the range [0,86400). func Times(p globe.Coord, ΔT unit.Time, h0 unit.Angle, Th0 unit.Time, α3 []unit.RA, δ3 []unit.Angle) (tRise, tTransit, tSet unit.Time, err error) { tRise, tTransit, tSet, err = ApproxTimes(p, h0, Th0, α3[1], δ3[1]) if err != nil { return } αf := make([]float64, 3) for i, α := range α3 { αf[i] = α.Rad() } δf := make([]float64, 3) for i, δ := range δ3 { δf[i] = δ.Rad() } var d3α, d3δ *interp.Len3 d3α, err = interp.NewLen3(-86400, 86400, αf) if err != nil { return } d3δ, err = interp.NewLen3(-86400, 86400, δf) if err != nil { return } // adjust tTransit { th0 := (Th0 + tTransit.Mul(360.985647/360)).Mod1() α := d3α.InterpolateX((tTransit + ΔT).Sec()) // local hour angle as Time H := th0 - unit.TimeFromRad(p.Lon.Rad()+α) tTransit -= H } // adjust tRise, tSet sLat, cLat := p.Lat.Sincos() adjustRS := func(m unit.Time) (unit.Time, error) { th0 := (Th0 + m.Mul(360.985647/360)).Mod1() ut := (m + ΔT).Sec() α := d3α.InterpolateX(ut) δ := d3δ.InterpolateX(ut) Hrad := th0.Rad() - p.Lon.Rad() - α sδ, cδ := math.Sincos(δ) sH, cH := math.Sincos(Hrad) h := math.Asin(sLat*sδ + cLat*cδ*cH) md := (unit.TimeFromRad(h) - h0.Time()).Div(cδ * cLat * sH) return m + md, nil } tRise, err = adjustRS(tRise) if err != nil { return } tSet, err = adjustRS(tSet) return }
func (m *moon) pa(λ, β, b unit.Angle) unit.Angle { V := m.Ω + m.Δψ + m.σ.Div(sI) sV, cV := V.Sincos() sIρ, cIρ := (_I + m.ρ).Sincos() X := sIρ * sV Y := sIρ*cV*m.cε - cIρ*m.sε ω := math.Atan2(X, Y) α, _ := coord.EclToEq(λ+m.Δψ, β, m.sε, m.cε) P := unit.Angle(math.Asin(math.Hypot(X, Y) * math.Cos(α.Rad()-ω) / b.Cos())) if P < 0 { P += 2 * math.Pi } return P }
// Kepler2b solves Kepler's equation by iteration. // // The iterated formula is the same as in Kepler2 but a (different) limiting // function avoids divergence. // // Argument e is eccentricity, M is mean anomaly, // places is the desired number of decimal places in the result. // // Result E is eccentric anomaly. func Kepler2b(e float64, M unit.Angle, places int) (E unit.Angle, err error) { f := func(E0 float64) float64 { se, ce := math.Sincos(E0) d := (M.Rad() + e*se - E0) / (1 - e*ce) // method of Steele, p. 205 if d > .5 { d = .5 } else if d < -.5 { d = -.5 } return E0 + d } ea, err := iterate.DecimalPlaces(f, M.Rad(), places, places) return unit.Angle(ea), err }
// TopocentricEcliptical returns topocentric ecliptical coordinates including parallax. // // Arguments λ, β are geocentric ecliptical longitude and latitude of a body, // s is its geocentric semidiameter. φ, h are the observer's latitude and // and height above the ellipsoid in meters. ε is the obliquity of the // ecliptic, θ is local sidereal time, π is equatorial horizontal parallax // of the body (see Horizonal()). // // Results are observed topocentric coordinates and semidiameter. func TopocentricEcliptical(λ, β, s, φ unit.Angle, h float64, ε unit.Angle, θ unit.Time, π unit.Angle) (λʹ, βʹ, sʹ unit.Angle) { S, C := globe.Earth76.ParallaxConstants(φ, h) sλ, cλ := λ.Sincos() sβ, cβ := β.Sincos() sε, cε := ε.Sincos() sθ, cθ := θ.Angle().Sincos() sπ := π.Sin() N := cλ*cβ - C*sπ*cθ λʹ = unit.Angle(math.Atan2(sλ*cβ-sπ*(S*sε+C*cε*sθ), N)) if λʹ < 0 { λʹ += 2 * math.Pi } cλʹ := λʹ.Cos() βʹ = unit.Angle(math.Atan(cλʹ * (sβ - sπ*(S*cε-C*sε*sθ)) / N)) sʹ = unit.Angle(math.Asin(cλʹ * βʹ.Cos() * s.Sin() / N)) return }
// Time computes the time at which a moving body is on a straight line (great // circle) between two fixed points, such as stars. // // Coordinates may be right ascensions and declinations or longitudes and // latitudes. Fixed points are r1, d1, r2, d2. Moving body is an ephemeris // of 5 rows, r3, d3, starting at time t1 and ending at time t5. Time scale // is arbitrary. // // Result is time of alignment. func Time(r1, d1, r2, d2 unit.Angle, r3, d3 []unit.Angle, t1, t5 float64) (float64, error) { if len(r3) != 5 || len(d3) != 5 { return 0, errors.New("r3, d3 must be length 5") } gc := make([]float64, 5) for i, r3i := range r3 { // (19.1) p. 121 gc[i] = d1.Tan()*(r2-r3i).Sin() + d2.Tan()*(r3i-r1).Sin() + d3[i].Tan()*(r1-r2).Sin() } l5, err := interp.NewLen5(t1, t5, gc) if err != nil { return 0, err } return l5.Zero(false) }
// Position computes apparent position angle and angular distance of // components of a binary star. // // e is eccentricity of the true orbit // a is angular apparent semimajor axis // i is inclination relative to the line of sight // Ω is position angle of the ascending node // ω is longitude of periastron // E is eccentric anomaly, computed for example with package kepler // and the mean anomaly as returned by function M in this package. // // Return value θ is the apparent position angle, ρ is the angular distance. func Position(e float64, a, i, Ω, ω, E unit.Angle) (θ, ρ unit.Angle) { r := a.Mul(1 - e*E.Cos()) ν := unit.Angle(2 * math.Atan(math.Sqrt((1+e)/(1-e))*E.Div(2).Tan())) sνω, cνω := (ν + ω).Sincos() ci := i.Cos() num := sνω * ci θ = (unit.Angle(math.Atan2(num, cνω)) + Ω).Mod1() ρ = r.Mul(math.Sqrt(num*num + cνω*cνω)) return }
// ApproxTimes computes approximate UT rise, transit and set times for // a celestial object on a day of interest. // // The function argurments do not actually include the day, but do include // values computed from the day. // // p is geographic coordinates of observer. // h0 is "standard altitude" of the body. // Th0 is apparent sidereal time at 0h UT at Greenwich. // α, δ are right ascension and declination of the body. // // Th0 must be the time on the day of interest. // See sidereal.Apparent0UT. // // α, δ must be values at 0h dynamical time for the day of interest. func ApproxTimes(p globe.Coord, h0 unit.Angle, Th0 unit.Time, α unit.RA, δ unit.Angle) (tRise, tTransit, tSet unit.Time, err error) { // approximate local hour angle sLat, cLat := p.Lat.Sincos() sδ1, cδ1 := δ.Sincos() cH0 := (h0.Sin() - sLat*sδ1) / (cLat * cδ1) // (15.1) p. 102 if cH0 < -1 || cH0 > 1 { err = ErrorCircumpolar return } H0 := unit.TimeFromRad(math.Acos(cH0)) // approximate transit, rise, set times. // (15.2) p. 102. mt := unit.TimeFromRad(α.Rad()+p.Lon.Rad()) - Th0 tTransit = mt.Mod1() tRise = (mt - H0).Mod1() tSet = (mt + H0).Mod1() return }
// Angle returns the angle between great circles defined by three points. // // Coordinates may be right ascensions and declinations or longitudes and // latitudes. If r1, d1, r2, d2 defines one line and r2, d2, r3, d3 defines // another, the result is the angle between the two lines. // // Algorithm by Meeus. func Angle(r1, d1, r2, d2, r3, d3 unit.Angle) unit.Angle { sd2, cd2 := d2.Sincos() sr21, cr21 := (r2 - r1).Sincos() sr32, cr32 := (r3 - r2).Sincos() C1 := math.Atan2(sr21, cd2*d1.Tan()-sd2*cr21) C2 := math.Atan2(sr32, cd2*d3.Tan()-sd2*cr32) return unit.Angle(C1 + C2) }
// Kepler3 solves Kepler's equation by binary search. // // Argument e is eccentricity, M is mean anomaly. // // Result E is eccentric anomaly. func Kepler3(e float64, M unit.Angle) (E unit.Angle) { // adapted from BASIC, p. 206 MR := M.Mod1().Rad() f := 1 if MR > math.Pi { f = -1 MR = 2*math.Pi - MR } E0 := math.Pi * .5 d := math.Pi * .25 for i := 0; i < 53; i++ { M1 := E0 - e*math.Sin(E0) if MR-M1 < 0 { E0 -= d } else { E0 += d } d *= .5 } if f < 0 { E0 = -E0 } return unit.Angle(E0) }
// General computes data for the general case of a planar sundial. // // Argument φ is geographic latitude at which the sundial will be located. // D is gnomonic declination, the azimuth of the perpendicular to the plane // of the sundial, measured from the southern meridian towards the west. // Argument a is the length of a straight stylus perpendicular to the plane // of the sundial, z is zenithal distance of the direction defined by the // stylus. Units of stylus length a are arbitrary. // // Results consist of a set of lines, a center point, u, the length of a // polar stylus, and ψ, the angle which the polar stylus makes with the plane // of the sundial. The center point, the points defining the hour lines, and // u are in units of a, the stylus length. func General(φ, D unit.Angle, a float64, z unit.Angle) (lines []Line, center Point, u float64, ψ unit.Angle) { sφ, cφ := φ.Sincos() tφ := sφ / cφ sD, cD := D.Sincos() sz, cz := z.Sincos() P := sφ*cz - cφ*sz*cD for i := 0; i < 24; i++ { l := Line{Hour: i} H := float64(i-12) * 15 * math.Pi / 180 aH := math.Abs(H) sH, cH := math.Sincos(H) for _, d := range m { tδ := math.Tan(d * math.Pi / 180) H0 := math.Acos(-tφ * tδ) if aH > H0 { continue // sun below horizon } Q := sD*sz*sH + (cφ*cz+sφ*sz*cD)*cH + P*tδ if Q < 0 { continue // sun below plane of sundial } Nx := cD*sH - sD*(sφ*cH-cφ*tδ) Ny := cz*sD*sH - (cφ*sz-sφ*cz*cD)*cH - (sφ*sz+cφ*cz*cD)*tδ l.Points = append(l.Points, Point{a * Nx / Q, a * Ny / Q}) } if len(l.Points) > 0 { lines = append(lines, l) } } center.X = a / P * cφ * sD center.Y = -a / P * (sφ*sz + cφ*cz*cD) aP := math.Abs(P) u = a / aP ψ = unit.Angle(math.Asin(aP)) return }
// Smallest finds the smallest circle containing three points. // // Arguments should represent coordinates in right ascension and declination // or longitude and latitude. Result Δ is the diameter of the circle, typeI // is true if solution is of type I. // // type I Two points on circle, one interior. // type II All three points on circle. func Smallest(r1, d1, r2, d2, r3, d3 unit.Angle) (Δ unit.Angle, typeI bool) { // Using haversine formula, but reimplementing SepHav here to reuse // the computed cosines. cd1 := d1.Cos() cd2 := d2.Cos() cd3 := d3.Cos() a := 2 * math.Asin(math.Sqrt(base.Hav(d2-d1)+cd1*cd2*base.Hav(r2-r1))) b := 2 * math.Asin(math.Sqrt(base.Hav(d3-d2)+cd2*cd3*base.Hav(r3-r2))) c := 2 * math.Asin(math.Sqrt(base.Hav(d1-d3)+cd3*cd1*base.Hav(r1-r3))) if b > a { a, b = b, a } if c > a { a, c = c, a } if a*a >= b*b+c*c { return unit.Angle(a), true } // (20.1) p. 128 return unit.Angle(2 * a * b * c / math.Sqrt((a+b+c)*(a+b-c)*(b+c-a)*(a+c-b))), false }
// Physical computes quantities for physical observations of Jupiter. // // Results: // DS Planetocentric declination of the Sun. // DE Planetocentric declination of the Earth. // ω1 Longitude of the System I central meridian of the illuminated disk, // as seen from Earth. // ω2 Longitude of the System II central meridian of the illuminated disk, // as seen from Earth. // P Geocentric position angle of Jupiter's northern rotation pole. func Physical(jde float64, earth, jupiter *pp.V87Planet) (DS, DE, ω1, ω2, P unit.Angle) { // Step 1. d := jde - 2433282.5 T1 := d / base.JulianCentury const p = math.Pi / 180 α0 := 268*p + .1061*p*T1 δ0 := 64.5*p - .0164*p*T1 // Step 2. W1 := 17.71*p + 877.90003539*p*d W2 := 16.838*p + 870.27003539*p*d // Step 3. l0, b0, R := earth.Position(jde) l0, b0 = pp.ToFK5(l0, b0, jde) // Steps 4-7. sl0, cl0 := l0.Sincos() sb0 := b0.Sin() Δ := 4. // surely better than 0. var l, b unit.Angle var r, x, y, z float64 f := func() { τ := base.LightTime(Δ) l, b, r = jupiter.Position(jde - τ) l, b = pp.ToFK5(l, b, jde) sb, cb := b.Sincos() sl, cl := l.Sincos() // (42.2) p. 289 x = r*cb*cl - R*cl0 y = r*cb*sl - R*sl0 z = r*sb - R*sb0 // (42.3) p. 289 Δ = math.Sqrt(x*x + y*y + z*z) } f() f() // Step 8. ε0 := nutation.MeanObliquity(jde) // Step 9. sε0, cε0 := ε0.Sincos() sl, cl := l.Sincos() sb, cb := b.Sincos() αs := math.Atan2(cε0*sl-sε0*sb/cb, cl) δs := math.Asin(cε0*sb + sε0*cb*sl) // Step 10. sδs, cδs := math.Sincos(δs) sδ0, cδ0 := math.Sincos(δ0) DS = unit.Angle(math.Asin(-sδ0*sδs - cδ0*cδs*math.Cos(α0-αs))) // Step 11. u := y*cε0 - z*sε0 v := y*sε0 + z*cε0 α := math.Atan2(u, x) δ := math.Atan(v / math.Hypot(x, u)) sδ, cδ := math.Sincos(δ) sα0α, cα0α := math.Sincos(α0 - α) ζ := math.Atan2(sδ0*cδ*cα0α-sδ*cδ0, cδ*sα0α) // Step 12. DE = unit.Angle(math.Asin(-sδ0*sδ - cδ0*cδ*math.Cos(α0-α))) // Step 13. ω1 = unit.Angle(W1 - ζ - 5.07033*p*Δ) ω2 = unit.Angle(W2 - ζ - 5.02626*p*Δ) // Step 14. C := unit.Angle((2*r*Δ + R*R - r*r - Δ*Δ) / (4 * r * Δ)) if (l - l0).Sin() < 0 { C = -C } ω1 = (ω1 + C).Mod1() ω2 = (ω2 + C).Mod1() // Step 15. Δψ, Δε := nutation.Nutation(jde) ε := ε0 + Δε // Step 16. sε, cε := ε.Sincos() sα, cα := math.Sincos(α) α += .005693 * p * (cα*cl0*cε + sα*sl0) / cδ δ += .005693 * p * (cl0*cε*(sε/cε*cδ-sα*sδ) + cα*sδ*sl0) // Step 17. tδ := sδ / cδ Δα := (cε+sε*sα*tδ)*Δψ.Rad() - cα*tδ*Δε.Rad() Δδ := sε*cα*Δψ.Rad() + sα*Δε.Rad() αʹ := α + Δα δʹ := δ + Δδ sα0, cα0 := math.Sincos(α0) tδ0 := sδ0 / cδ0 Δα0 := (cε+sε*sα0*tδ0)*Δψ.Rad() - cα0*tδ0*Δε.Rad() Δδ0 := sε*cα0*Δψ.Rad() + sα0*Δε.Rad() α0ʹ := α0 + Δα0 δ0ʹ := δ0 + Δδ0 // Step 18. sδʹ, cδʹ := math.Sincos(δʹ) sδ0ʹ, cδ0ʹ := math.Sincos(δ0ʹ) sα0ʹαʹ, cα0ʹαʹ := math.Sincos(α0ʹ - αʹ) // (42.4) p. 290 P = unit.Angle(math.Atan2(cδ0ʹ*sα0ʹαʹ, sδ0ʹ*cδʹ-cδ0ʹ*sδʹ*cα0ʹαʹ)) if P < 0 { P += 2 * math.Pi } return }
// Illuminated returns the illuminated fraction of a body's disk. // // The illuminated body can be the Moon or a planet. // // Argument i is the phase angle. func Illuminated(i unit.Angle) float64 { // (41.1) p. 283, also (48.1) p. 345. return (1 + i.Cos()) * .5 }
// SunAltitude returns altitude of the Sun above the lunar horizon. // // Arguments η, θ are selenographic longitude and latitude of a site on the // Moon, l0, b0 are selenographic coordinates of the Sun, as returned by // Physical(), for example. func SunAltitude(η, θ, l0, b0 unit.Angle) unit.Angle { c0 := math.Pi/2 - l0 sb0, cb0 := b0.Sincos() sθ, cθ := θ.Sincos() return unit.Angle(math.Asin(sb0*sθ + cb0*cθ*(c0+η).Sin())) }
// Radius returns radius distance r for given eccentric anomaly E. // // Argument e is eccentricity, a is semimajor axis. // // Result unit is the unit of semimajor axis a (typically AU.) func Radius(E unit.Angle, e, a float64) float64 { // (30.2) p. 195 return a * (1 - e*E.Cos()) }
// True returns true anomaly ν for given eccentric anomaly E. // // Argument e is eccentricity. E must be in radians. func True(E unit.Angle, e float64) unit.Angle { // (30.1) p. 195 return unit.Angle(2 * math.Atan(math.Sqrt((1+e)/(1-e))*E.Mul(.5).Tan())) }
// Kepler4 returns an approximate solution to Kepler's equation. // // It is valid only for small values of e. // // Argument e is eccentricity, M is mean anomaly. // // Result E is eccentric anomaly. func Kepler4(e float64, M unit.Angle) (E unit.Angle) { sm, cm := M.Sincos() return unit.Angle(math.Atan2(sm, cm-e)) // (30.8) p. 206 }
// SepHav returns the angular separation between two celestial bodies. // // The algorithm uses the haversine function and is superior to the naïve // algorithm of the Sep function. func SepHav(r1, d1, r2, d2 unit.Angle) unit.Angle { // using (17.5) p. 115 return unit.Angle(2 * math.Asin(math.Sqrt(base.Hav(d2-d1)+ d1.Cos()*d2.Cos()*base.Hav(r2-r1)))) }
// Hav implements the haversine trigonometric function. // // See https://en.wikipedia.org/wiki/Haversine_formula. func Hav(a unit.Angle) float64 { // (17.5) p. 115 return .5 * (1 - a.Cos()) }
// RelativePosition returns the position angle of one body with respect to // another. // // The position angle result is measured counter-clockwise from North. func RelativePosition(r1, d1, r2, d2 unit.Angle) unit.Angle { sΔr, cΔr := (r2 - r1).Sincos() sd2, cd2 := d2.Sincos() return unit.Angle(math.Atan2(sΔr, cd2*d1.Tan()-sd2*cΔr)) }