Files
astro/doc/manual/en/orbit.md
T
b612 16c62a97d5 feat: 完善时标与天象几何计算并扩展输出接口
- 新增时标、ΔT 模型、质心时间与 UT1 支持
- 改进日月食、月掩、行星事件及路径边界计算
- 完善恒星三维自行与动态距离传播
- 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具
- 整理中英文手册、示例资源及回归测试
2026-09-23 18:55:12 +08:00

462 lines
25 KiB
Markdown

# Generic Small-Body Orbits
[中文](../orbit.md) | [Back to README](../../../README.en.md)
`orbit` propagates heliocentric two-body positions from orbital elements. It supports asteroids, comets, dwarf planets, and custom hypothetical orbits.
The seven major planets are still computed by their own packages using built-in VSOP87 analytical terms.
`orbit.Elements` supports two common forms:
- classical elliptical elements: `A/E/I/Omega/W/M0`
- perihelion form: `Q/E/I/Omega/W/TpJD`, useful for comets and high-eccentricity orbits
The reference frame of `orbit.Elements` is always the J2000 mean ecliptic and mean equinox. The examples below use one set of Ceres elements.
Topocentric and rise/set helpers take east-positive longitude, north-positive latitude, and height in meters.
The positional and topocentric quantities here also work together with the interfaces documented in the [star](star.md#stars) and [coordinate tools](coord.md#coordinate-tools) manuals.
## Contents
- [Calculating the position of Ceres from orbital elements](#calculating-the-position-of-ceres-from-orbital-elements)
- [API Reference](#api-reference)
- [Orbital Elements](#orbital-elements)
- [Positions](#positions)
- [Geometry](#geometry)
- [Rise, Set, and Culmination](#rise-set-and-culmination)
- [Photometry](#photometry)
- [Visual Binaries](#visual-binaries)
- [Usage examples](#usage-examples)
- [Picking a position layer](#picking-a-position-layer)
- [Will it rise tonight, and how high is it now?](#will-it-rise-tonight-and-how-high-is-it-now)
- [Distances, phase angle and H-G magnitude](#distances-phase-angle-and-h-g-magnitude)
- [Orbit types and position layers](#orbit-types-and-position-layers)
- [Parameter and result conventions](#parameter-and-result-conventions)
- [Position Layers](#position-layers)
- [Units and Frame Conventions](#units-and-frame-conventions)
- [Time Scale and Input Reading](#time-scale-and-input-reading)
- [Zero Values and Out-of-Range Input](#zero-values-and-out-of-range-input)
- [Accuracy and Applicability](#accuracy-and-applicability)
- [Common Pitfalls](#common-pitfalls)
- [Related Manuals](#related-manuals)
## Calculating the position of Ceres from orbital elements
```go
package main
import (
"fmt"
"log"
"time"
"b612.me/astro/orbit"
)
func main() {
cst := time.FixedZone("CST", 8*3600)
when := time.Date(2025, 11, 21, 20, 0, 0, 0, cst)
ceres := orbit.Elements{
EpochJD: 2461000.5, A: 2.765615651508659, E: 0.07957631994408416,
I: 10.58788658206854, Omega: 80.24963090816965,
W: 73.29975464616518, M0: 231.5397330043706,
}
pos := orbit.ApparentGeocentricEquatorial(when, ceres)
fmt.Printf("RA=%.6f Dec=%.6f deg distance=%.6f AU\n", pos.RA, pos.Dec, pos.Distance)
rise, err := orbit.RiseTime(when, ceres, 121.4737, 31.2304, 20, true)
if err != nil {
log.Fatal(err)
}
fmt.Println(rise.Format(time.RFC3339))
}
```
Elements use the J2000 mean ecliptic frame and a TT/TDB Julian-day epoch. This is heliocentric two-body propagation; perturbation errors need separate evaluation over long spans or near planetary encounters.
## API Reference
### Orbital Elements
| Name | Purpose | Units and convention |
| --- | --- | --- |
| `Elements` | Heliocentric two-body conic elements | `EpochJD`/`TpJD` are TT/TDB Julian days; `A`/`Q` in AU, `I`/`Omega`/`W`/`M0` in degrees, `E` dimensionless; `ADot`…`MDot` are per-day rates that apply to the classical elliptical form only |
| `MeanMotion` | Mean angular rate | degrees/day; `NaN` for parabolic and hyperbolic cases; a non-zero `MDot` is used directly |
| `MeanAnomaly` | Mean anomaly | degrees (`[0,360)`); `NaN` for parabolic and hyperbolic cases |
| `TrueAnomaly` | True anomaly | degrees (`[0,360)`); defined for elliptical, parabolic, and hyperbolic orbits, `NaN` for invalid elements |
Mean anomaly and true anomaly are solved from the same element set, and `MDot` can replace the default mean motion:
```go
fmt.Println(orbit.MeanMotion(ceres), orbit.MeanAnomaly(when, ceres), orbit.TrueAnomaly(when, ceres))
// Parabolic and hyperbolic orbits only accept the perihelion form; mean motion and mean anomaly are undefined.
parabolic := orbit.Elements{Q: 0.9, E: 1, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
hyperbolic := orbit.Elements{Q: 1.2, E: 1.05, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
fmt.Println(orbit.MeanMotion(parabolic), orbit.MeanAnomaly(when, parabolic))
fmt.Println(orbit.TrueAnomaly(when, parabolic), orbit.TrueAnomaly(when, hyperbolic))
```
The complete example exercises both the elliptical and the perihelion element form:
```go
package main
import (
"fmt"
"time"
"b612.me/astro/orbit"
)
func main() {
// Classical elliptical elements for 1 Ceres, referenced to J2000 mean ecliptic/equinox.
ceres := orbit.Elements{
EpochJD: 2461000.5,
A: 2.765615651508659,
E: 0.07957631994408416,
I: 10.58788658206854,
Omega: 80.24963090816965,
W: 73.29975464616518,
M0: 231.5397330043706,
}
ceresPos := orbit.ApparentGeocentricEquatorial(
time.Date(2025, 11, 12, 0, 0, 0, 0, time.UTC),
ceres,
)
fmt.Printf("ceres ra=%.6f dec=%.6f distance=%.6f\n", ceresPos.RA, ceresPos.Dec, ceresPos.Distance)
// Halley's Comet example using perihelion distance Q and perihelion passage time TpJD.
halley := orbit.Elements{
Q: 0.5870992,
E: 0.9671429,
I: 162.26269,
Omega: 58.42008,
W: 111.33249,
TpJD: 2446467.395,
}
halleyPos := orbit.ApparentGeocentricEquatorial(
time.Date(1986, 2, 9, 0, 0, 0, 0, time.UTC),
halley,
)
fmt.Printf("halley ra=%.6f dec=%.6f distance=%.6f\n", halleyPos.RA, halleyPos.Dec, halleyPos.Distance)
}
```
Output:
```text
ceres ra=7.739532 dec=-10.625981 distance=2.164391
halley ra=312.112360 dec=-11.826451 distance=1.533936
```
Orbital elements have epochs. The farther the target date is from the epoch, the more static-element error can grow.
If the source provides long-term linear rates such as `ADot/EDot/IDot/OmegaDot/WDot/MDot`, they can be filled into `Elements` to reduce medium- and long-term drift.
### Positions
| Name | Purpose | Units and convention |
| --- | --- | --- |
| `EclipticPosition` | Ecliptic spherical return value | `Lon`/`Lat` in degrees, `Distance` in AU |
| `EquatorialPosition` | Equatorial spherical return value | `RA`/`Dec` in degrees, `Distance` in AU |
| `HeliocentricEclipticJ2000` | Heliocentric J2000 mean ecliptic | geometric, no light-time |
| `HeliocentricEcliptic` | Heliocentric ecliptic of date | geometric, referred to the mean equinox of date |
| `GeocentricEclipticJ2000` | Geocentric J2000 mean ecliptic | geometric, Earth and target at the same instant |
| `GeocentricEcliptic` | Geocentric ecliptic of date | geometric, referred to the mean equinox of date |
| `GeocentricEquatorialJ2000` | Geocentric J2000 mean equatorial | geometric, J2000 obliquity |
| `GeocentricEquatorial` | Geocentric mean equatorial of date | geometric, obliquity of date |
| `AstrometricGeocentricEquatorialJ2000` | Astrometric geocentric J2000 equatorial | geometric position plus light-time, directly comparable with J2000 catalogues |
| `ApparentGeocentricEcliptic` | Apparent geocentric ecliptic | light-time plus nutation, without a full aberration model |
| `ApparentGeocentricEquatorial` | Apparent geocentric equatorial | light-time plus nutation, without a full aberration model |
| `ApparentTopocentricEquatorial` | Apparent topocentric equatorial | apparent geocentric plus topocentric parallax |
Each step down the chain adds one correction to the same instant — geometric, then light-time, then nutation, then topocentric:
```go
h := orbit.HeliocentricEcliptic(when, ceres) // heliocentric ecliptic of date, geometric
g := orbit.GeocentricEquatorialJ2000(when, ceres) // geocentric J2000 mean equatorial
a := orbit.ApparentGeocentricEquatorial(when, ceres) // apparent geocentric equatorial
t := orbit.ApparentTopocentricEquatorial(when, ceres, 121.4737, 31.2304, 20)
e := orbit.ApparentGeocentricEcliptic(when, ceres)
fmt.Printf("h=%.6f %.6f %.6f\n", h.Lon, h.Lat, h.Distance)
fmt.Printf("g=%.6f %.6f %.6f\n", g.RA, g.Dec, g.Distance)
fmt.Printf("a=%.6f %.6f t=%.6f %.6f e=%.6f\n", a.RA, a.Dec, t.RA, t.Dec, e.Lon)
```
The `...J2000` variants and the of-date variants are two different frames: the former stay in the J2000 mean ecliptic/equinox, which suits catalogue comparison and long-term archiving; the latter use the mean ecliptic/equinox of date, which suits "today's sky" expressions. `Distance` is the instantaneous distance for the geometric interfaces and the light-time-converged distance for `Astrometric...`; the two differ by roughly the displacement during the light-time.
### Geometry
| Name | Purpose | Units and convention |
| --- | --- | --- |
| `SunDistance` | Heliocentric distance | AU, geometric |
| `EarthDistance` | Geocentric distance | AU, geometric |
| `Elongation` | Solar elongation | degrees, apparent geocentric angular separation |
| `PhaseAngle` | Phase angle | degrees, 0° means the fully lit face points at the observer |
| `IlluminatedFraction` | Illuminated fraction | dimensionless, typically within `[0,1]` |
| `Phase` | Alias of the illuminated fraction | identical to `IlluminatedFraction` |
| `ParallacticAngle` | Parallactic (zenith-direction) angle | degrees, hour angle and declination from one and the same topocentric solve |
`orbit` also provides common observing geometry and lightweight photometry helpers:
```go
r := orbit.SunDistance(when, ceres) // heliocentric distance
delta := orbit.EarthDistance(when, ceres) // geocentric distance
elong := orbit.Elongation(when, ceres) // solar elongation
phase := orbit.PhaseAngle(when, ceres) // phase angle
k := orbit.IlluminatedFraction(when, ceres) // illuminated fraction
mag := orbit.AsteroidMagnitudeHG(when, ceres, 3.34, 0.12) // H-G asteroid magnitude
q := orbit.ParallacticAngle(when, ceres, 121.4737, 31.2304, 20) // parallactic angle from a site
fmt.Printf("r=%.6f delta=%.6f elong=%.6f phase=%.6f k=%.6f mag=%.3f q=%.6f\n",
r, delta, elong, phase, k, mag, q)
```
Near 180° elongation the phase angle approaches 0° and `IlluminatedFraction` approaches 1; all three quantities come from the same geocentric geometry.
`ParallacticAngle` does not solve for declination separately: it reuses the same topocentric solve as `HourAngle`, so that hour angle and declination never come from two Julian-day paths about 1 ULP apart and introduce sub-nanodegree drift.
All geometry helpers depend only on the absolute instant of `date`; `ParallacticAngle` is the only one that also takes observer parameters.
### Rise, Set, and Culmination
| Name | Purpose | Units and convention |
| --- | --- | --- |
| `Altitude` | Apparent altitude | degrees, apparent topocentric position on the observer's local civil day |
| `Zenith` | Zenith distance | degrees, equal to `90 - Altitude` |
| `Azimuth` | Apparent azimuth | degrees, north 0°, increasing toward east |
| `HourAngle` | Topocentric hour angle | degrees |
| `CulminationTime` | Culmination time | `time.Time`, keeps the location of the input `date` |
| `RiseTime` | Rise time | `(time.Time, error)`, second value is a sentinel error |
| `SetTime` | Set time | `(time.Time, error)`, second value is a sentinel error |
| `ERR_ORBIT_NEVER_RISE` | Sentinel: target never rises that day | returned by `RiseTime` |
| `ERR_ORBIT_NEVER_SET` | Sentinel: target never sets that day | returned by `SetTime` |
```go
fmt.Println(orbit.Zenith(when, ceres, 121.4737, 31.2304, 20))
fmt.Println(orbit.HourAngle(when, ceres, 121.4737, 31.2304, 20))
fmt.Println(orbit.CulminationTime(when, ceres, 121.4737, 31.2304, 20).Format(time.RFC3339))
day := time.Date(2025, 11, 21, 0, 0, 0, 0, site)
set, err := orbit.SetTime(day, ceres, 121.4737, 31.2304, 20, true)
if errors.Is(err, orbit.ERR_ORBIT_NEVER_SET) {
fmt.Println("never sets today", set)
}
```
Treat an orbit as an observable target for topocentric pointing:
```go
site := time.FixedZone("CST", 8*3600)
when := time.Date(2025, 11, 21, 20, 0, 0, 0, site)
alt := orbit.Altitude(when, ceres, 121.4737, 31.2304, 20) // topocentric altitude
az := orbit.Azimuth(when, ceres, 121.4737, 31.2304, 20) // topocentric azimuth
rise, _ := orbit.RiseTime(time.Date(2025, 11, 21, 0, 0, 0, 0, site), ceres, 121.4737, 31.2304, 20, true) // rise time
fmt.Printf("alt=%.6f az=%.6f rise=%s\n", alt, az, rise.Format(time.RFC3339))
```
These observing helpers work on topocentric apparent coordinates and suit rise/set and pointing support for asteroids, comets, or custom two-body targets.
With `aero` false the criterion is the geometric horizon; with `aero` true the target altitude is taken as `-0.5667°` plus the horizon dip derived from ellipsoidal height and latitude.
The second return value of `RiseTime`/`SetTime` is a real error: only a day without a rise/set is mapped to `ERR_ORBIT_NEVER_RISE` / `ERR_ORBIT_NEVER_SET`, and any other failure passes through unchanged.
### Photometry
| Name | Purpose | Units and convention |
| --- | --- | --- |
| `AsteroidMagnitudeHG` | Asteroid apparent magnitude, H-G model | `absoluteMagnitude` is H and `slopeParameter` is G; both dimensionless |
```go
fmt.Printf("H-G magnitude=%.3f\n", orbit.AsteroidMagnitudeHG(when, ceres, 3.34, 0.12))
fmt.Printf("r=%.6f delta=%.6f elong=%.6f\n",
orbit.SunDistance(when, ceres), orbit.EarthDistance(when, ceres), orbit.Elongation(when, ceres))
fmt.Printf("phase=%.6f k=%.6f k2=%.6f\n",
orbit.PhaseAngle(when, ceres), orbit.IlluminatedFraction(when, ceres), orbit.Phase(when, ceres))
```
The H-G model uses only heliocentric distance, geocentric distance, and phase angle; it does not introduce the target radius, albedo, or rotation, and this package never fills in a default value for `G` — that comes from the external catalogue.
### Visual Binaries
| Name | Purpose | Units and convention |
| --- | --- | --- |
| `VisualBinaryElements` | Visual-binary orbital elements | `PeriodYears` in mean solar years, `PeriastronYear` as a decimal year, `SemiMajorAxis` in arcseconds, `Inclination`/`AscendingNode`/`PeriastronArgument` in degrees, `Eccentricity` dimensionless |
| `VisualBinaryPosition` | Computed visual-binary position | `MeanAnomaly`/`EccentricAnomaly`/`TrueAnomaly`/`PositionAngle` in degrees, `Radius`/`Separation` in arcseconds |
| `VisualBinary` | Visual-binary position at an instant | converts the instant to a UTC decimal year, then applies the classical apparent-orbit formula |
| `VisualBinaryByYear` | Visual-binary position by decimal year | takes the decimal year directly, skipping the instant conversion |
```go
gammaVir := orbit.VisualBinaryElements{
PeriodYears: 171.37, PeriastronYear: 1836.433, Eccentricity: 0.8808,
SemiMajorAxis: 3.746, Inclination: 146.05, AscendingNode: 31.78, PeriastronArgument: 252.88,
}
vb := orbit.VisualBinaryByYear(2026.0, gammaVir)
fmt.Printf("theta=%.6f rho=%.6f M=%.6f\n", vb.PositionAngle, vb.Separation, vb.MeanAnomaly)
```
`orbit` also includes a lightweight visual-binary solver using the classical apparent-orbit formula from chapter 55 of *Astronomical Algorithms*:
```go
gammaVir := orbit.VisualBinaryElements{
PeriodYears: 171.37,
PeriastronYear: 1836.433,
Eccentricity: 0.8808,
SemiMajorAxis: 3.746,
Inclination: 146.05,
AscendingNode: 31.78,
PeriastronArgument: 252.88,
}
vb := orbit.VisualBinary(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), gammaVir)
fmt.Printf("theta=%.6f rho=%.6f\n", vb.PositionAngle, vb.Separation) // position angle and separation
```
Position angle is measured with north at 0° and east at 90°, and both the separation and the radius vector `Radius` are in arcseconds.
## Usage examples
### Picking a position layer
```go
helio := orbit.HeliocentricEcliptic(when, ceres)
geo := orbit.GeocentricEclipticJ2000(when, ceres)
ast := orbit.AstrometricGeocentricEquatorialJ2000(when, ceres)
app := orbit.ApparentGeocentricEquatorial(when, ceres)
fmt.Println(helio.Lon, helio.Lat, helio.Distance)
fmt.Println(geo.Lon, geo.Lat, geo.Distance)
fmt.Println(ast.RA, ast.Dec)
fmt.Println(app.RA, app.Dec, app.Distance)
```
```text
19.251489 -9.315340 2.912174
2.147652 -12.026350 2.262445
6.795211 -10.172420
7.125008 -10.028726 2.262489
```
The four layers mean different things: `Heliocentric*` is relative to the Sun (the first number is the heliocentric distance), `Geocentric*J2000` is the J2000 geocentric position, `Astrometric*J2000` removes light-time and suits catalog comparison, and `Apparent*` is the apparent position of the day used for observing and charts.
For a topocentric apparent position use `ApparentTopocentricEquatorial(when, ceres, lon, lat, height)`.
### Will it rise tonight, and how high is it now?
```go
fmt.Println(orbit.RiseTime(when, ceres, 121.4737, 31.2304, 20, true))
fmt.Println(orbit.CulminationTime(when, ceres, 121.4737, 31.2304, 20))
fmt.Println(orbit.Altitude(when, ceres, 121.4737, 31.2304, 20),
orbit.Azimuth(when, ceres, 121.4737, 31.2304, 20))
```
```text
2025-11-21 14:41:48.913 CST <nil>
2025-11-21 20:19:34 CST
48.472628 172.699168
```
- `aero = true` solves against the horizon corrected for refraction and apparent radius; `height` is ellipsoidal height in metres and longitude is east positive.
- Circumpolar or polar targets have no rise or set: `RiseTime`/`SetTime` return the `orbit.ERR_ORBIT_NEVER_RISE` / `ERR_ORBIT_NEVER_SET` sentinels, so branch with `errors.Is`.
### Distances, phase angle and H-G magnitude
```go
fmt.Println(orbit.SunDistance(when, ceres), orbit.EarthDistance(when, ceres))
fmt.Println(orbit.Elongation(when, ceres), orbit.PhaseAngle(when, ceres))
fmt.Println(orbit.IlluminatedFraction(when, ceres), orbit.AsteroidMagnitudeHG(when, ceres, 3.34, 0.12))
```
```text
2.912174 2.262445
122.264630 16.670089
0.978986 8.360
```
- `PhaseAngle` is the Sun-target-Earth angle in degrees and `IlluminatedFraction` is the illuminated fraction; `Phase` is only an alias of `IlluminatedFraction`, so do not read it as a phase angle.
- H-G magnitudes take the absolute magnitude `H` and the slope parameter `G` (Ceres uses `3.34` and `0.12` in the example).
### Orbit types and position layers
```go
parabolic := orbit.Elements{Q: 0.9, E: 1, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
hyperbolic := orbit.Elements{Q: 1.2, E: 1.05, I: 30, Omega: 40, W: 50, TpJD: 2461000.5}
fmt.Println(orbit.MeanMotion(parabolic), orbit.MeanAnomaly(when, parabolic))
fmt.Println(orbit.TrueAnomaly(when, parabolic), orbit.TrueAnomaly(when, hyperbolic))
fmt.Println(orbit.MeanMotion(ceres))
```
```text
NaN NaN
0.817533 0.537610
0.21429712142765137
```
- Parabolic and hyperbolic orbits only work in perihelion form (`Q` plus `TpJD`): mean motion and mean anomaly are undefined and return `NaN`, while the true anomaly solves for all three orbit types.
- When cross-checking against an external ephemeris, align the position layer and frame first (`Elements` is always J2000 mean ecliptic/equinox); `ADot…WDot` only apply to the classical ellipse form, and a non-zero `MDot` replaces the default mean motion.
## Parameter and result conventions
### Position Layers
| Layer | Representative interfaces | Corrections included |
| --- | --- | --- |
| Heliocentric geometric | `HeliocentricEcliptic` / `HeliocentricEclipticJ2000` | none |
| Geocentric geometric | `GeocentricEcliptic` / `GeocentricEclipticJ2000` / `GeocentricEquatorial` / `GeocentricEquatorialJ2000` | minus the heliocentric Earth position |
| Geocentric astrometric | `AstrometricGeocentricEquatorialJ2000` | light-time |
| Geocentric apparent | `ApparentGeocentricEcliptic` / `ApparentGeocentricEquatorial` | light-time + nutation |
| Topocentric apparent | `ApparentTopocentricEquatorial` and all rise/set helpers | light-time + nutation + topocentric parallax |
### Units and Frame Conventions
- Angles are always in degrees, distances in AU, ellipsoidal heights for topocentric and rise/set helpers in meters, and time as `time.Time`.
- The frame of `Elements` is the J2000 mean ecliptic and mean equinox. Interfaces ending in `...J2000` keep that frame; the other `HeliocentricEcliptic`/`GeocentricEcliptic`/`GeocentricEquatorial` variants are of-date quantities, so do not mix the two frames.
- Positions come in three layers: `Heliocentric*`/`Geocentric*` are geometric, `AstrometricGeocentricEquatorialJ2000` adds light-time to the geometric position (solved iteratively in distance, at most 8 passes, converged at `1e-12` days), and `Apparent*` adds nutation on top of light-time.
The planetary convention of this repository is exactly "light-time + nutation, without a full external aberration model", so `Apparent*` is at the same level as the planet packages and must not be treated as a full apparent place.
- `MeanMotion`/`MeanAnomaly`/`TrueAnomaly` return degrees; mean motion and mean anomaly are undefined for parabolic and hyperbolic orbits.
### Time Scale and Input Reading
- `EpochJD` and `TpJD` are TT/TDB Julian days. The coordinate, geometry, and photometry interfaces treat `date` as an absolute instant (`date.UTC()`, then `UTC2TT` to TT/TDB).
`UTC2TT` treats civil values as UT1 before 1972-01-01 and uses the built-in leap-second table inside the exact window; that table can be overridden with `astro.SetTTMinusUTC`.
- Instantaneous topocentric quantities combine the local clock fields with `date.Zone()` to recover the absolute instant. UTC and local-zone representations of the same instant give the same position. Rise/set searches also use the local date, so choose the zone for the observing calendar.
- `CulminationTime`, `RiseTime`, and `SetTime` keep the zone of the input `date` and return civil instants. Internally they step back 12 hours when `date.Hour() > 12`, so that the search anchor stays within the selected date.
- Every public `time.Time` output is a civil value (UTC label); UT1 and TT only appear in internal conversions. For explicit conversion use the root package's `astro.UT1FromUTC` / `astro.TTFromUTC`.
### Zero Values and Out-of-Range Input
- The zero value of `Elements` is not a valid orbit. The classical elliptical form requires a finite positive `A`, `E` within `[0,1)`, and finite `EpochJD` and `M0`.
Parabolic and hyperbolic orbits with `E >= 1` can only use the perihelion form, which requires a finite positive `Q`, a finite `TpJD`, `E >= 0`, and finite `I`/`Omega`/`W`.
- When `Q > 0` and `TpJD` is finite the perihelion form wins: `A`, `M0`, and `EpochJD` are ignored, `ADot`/`EDot`/`IDot`/`OmegaDot`/`WDot` have no effect, and only a non-zero `MDot` is used as the mean angular rate.
- With invalid elements `MeanMotion` and `MeanAnomaly` return `NaN` and the positional interfaces return three `NaN`s.
`AsteroidMagnitudeHG` returns `NaN` when its inputs are non-finite or when heliocentric distance, geocentric distance, or phase angle is non-positive, and `+Inf` when the H-G phase blend is zero.
- `RiseTime` and `SetTime` return the sentinel errors `ERR_ORBIT_NEVER_RISE` / `ERR_ORBIT_NEVER_SET` when no rise/set exists on the supplied local day, and pass any other failure through; on success the second return value is `nil`.
- `VisualBinary` and `VisualBinaryByYear` fill every numeric field of `VisualBinaryPosition` with `NaN` when `PeriodYears <= 0`, `SemiMajorAxis <= 0`, `Eccentricity` outside `[0,1)`, or any element is non-finite.
### Accuracy and Applicability
- `orbit` is two-body conic propagation: it contains only the supplied elements, with no planetary perturbations, non-gravitational terms, or relativistic corrections. The farther the date from the epoch, the larger the static-element error; `ADot`…`WDot` only mitigate linear drift and cannot replace a re-fit or a numerical integration.
- The difference between the `Apparent*` and topocentric quantities is purely geometric plus nutation, and neither includes atmospheric refraction; only `aero=true` in the rise/set helpers folds refraction into the criterion (target altitude `-0.5667°` plus the horizon dip derived from ellipsoidal height and latitude).
- `RiseTime`/`SetTime` iterate the rise/set geometry in the nominal zone `round(observerLon/15)`, so the site should sit near the center of its zone; only one root is solved, and boundary cases such as polar or circumpolar targets are reported through the sentinel errors.
- The visual-binary solver uses the classical apparent-orbit formula from chapter 55 of *Astronomical Algorithms* and does not apply when `Eccentricity >= 1`; it is a geometric projection only, with no mass, photometry, or perturbation information.
### Common Pitfalls
- Treating a `HeliocentricEcliptic` result as geocentric: heliocentric quantities are centered on the Sun, while geocentric ones have already subtracted the heliocentric Earth position.
- Treating `Apparent*` as a refraction-corrected apparent place: refraction appears only in the rise/set and observing helpers, not in the coordinate interfaces.
- Probing with a zero-value `Elements`: mean motion and the positional interfaces quietly return `NaN` instead of reporting an error.
- Expecting `ADot`…`WDot` to take effect in the perihelion form: those rates only propagate in the classical elliptical form.
## Related Manuals
- Stars and catalogues: [star](star.md#stars)
- Frame conversion and topocentric quantities: [coordinate tools](coord.md#coordinate-tools)
- The analogous rise/set interfaces: [Sun and Moon](sun-moon.md#sunrisesunset-and-moonrisemoonset)
> To feed the ecliptic/equatorial coordinates here into a star catalogue or topocentric quantities, see [star](star.md#stars) and [coordinate tools](coord.md#coordinate-tools).