- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
28 KiB
Coordinate Tools
coord provides celestial coordinate conversion and observing helper calculations. Unless noted otherwise, angles are degrees, sidereal time is in hours, and time.Time is treated as an absolute instant and internally converted to UTC.
Altitude, zenith distance, and horizon-visibility semantics follow Observing-angle semantics; full topocentric usage appears in Occultation · Local event chart and Sun and Moon positions;
topocentric solar-eclipse contacts appear in Solar eclipses.
Time scales, observer height, and overall accuracy are documented in Time scale conventions, Observer height conventions, and Applicability and accuracy.
Contents
- Converting ecliptic coordinates to equatorial and horizontal
- API Reference
- Usage examples
- Research APIs and Observing Helpers
- Parameter and result conventions
- Observer height
Converting ecliptic coordinates to equatorial and horizontal
package main
import (
"fmt"
"time"
"b612.me/astro/coord"
)
func main() {
cst := time.FixedZone("CST", 8*3600)
date := time.Date(2026, 4, 27, 10, 30, 45, 0, cst)
eq := coord.EclipticToEquatorial(date, 139.686111, 4.875278)
hz := coord.EquatorialToHorizontal(date, eq.RA, eq.Dec, 115, 40)
fmt.Printf("RA=%.6f Dec=%.6f deg\n", eq.RA, eq.Dec)
fmt.Printf("azimuth=%.6f altitude=%.6f deg\n", hz.Azimuth, hz.Altitude)
fmt.Printf("GAST=%.6f h\n", coord.ApparentSiderealTime(date))
}
Right ascension, declination and horizontal angles are in degrees; sidereal time is in hours. These equatorial coordinates belong to the observing date. Galactic conversion requires ICRS coordinates, so the two must not be mixed directly.
API Reference
The tables group functions by the quantity being calculated.
The snippets reuse date, cst and eq from the first example. The sidereal-time example also needs the standard math package.
Ecliptic ↔ Equatorial
| Name | Purpose | Units and conventions |
|---|---|---|
EclipticToEquatorial |
ecliptic to equatorial | ecliptic longitude/latitude and right ascension/declination in degrees; uses the true obliquity at that instant, RA ∈ [0,360) |
EquatorialToEcliptic |
equatorial to ecliptic | inverse of the same obliquity; Lon ∈ [0,360), Lat ∈ [−90,90] |
EclipticToEquatorialByObliquity |
ecliptic to equatorial with manual obliquity | all three parameters in degrees; for experiments with custom axial tilts, no date conversion |
EquatorialToEclipticByObliquity |
equatorial to ecliptic with manual obliquity | same; Lon ∈ [0,360), Lat ∈ [−90,90] |
Ecliptic |
ecliptic result value | fields Lon and Lat, in degrees |
Equatorial |
equatorial result value | fields RA and Dec, in degrees |
// date is the shared preamble; take the true obliquity at that instant, then compare with the date-based path.
obliquity := coord.EclipticObliquity(date, true)
auto := coord.EclipticToEquatorial(date, 139.686111, 4.875278)
manual := coord.EclipticToEquatorialByObliquity(139.686111, 4.875278, obliquity)
back := coord.EquatorialToEclipticByObliquity(manual.RA, manual.Dec, obliquity)
fmt.Printf("auto=(%.9f, %.9f) manual=(%.9f, %.9f)\n",
auto.RA, auto.Dec, manual.RA, manual.Dec)
fmt.Printf("back=(%.9f, %.9f)\n", back.Lon, back.Lat)
Equatorial ↔ Horizontal
| Name | Purpose | Units and conventions |
|---|---|---|
EquatorialToHorizontal |
apparent equatorial to horizontal | longitude east-positive, latitude north-positive, degrees; uses apparent sidereal time (with nutation); Azimuth ∈ [0,360), Altitude ∈ [−90,90], Zenith = 90 − Altitude |
EquatorialToHorizontalByLocalSiderealTime |
equatorial to horizontal with manual sidereal time | sidereal time in hours, multiplied by 15 internally; no date lookup, suitable for manual experiments |
HourAngleDeclinationToHorizontal |
hour angle plus declination to horizontal | input hour angle normalized into [0,360); Azimuth ∈ [0,360) |
HorizontalToHourAngleDeclination |
horizontal to hour angle plus declination | returned hour angle is normalized into [0,360) (not [−180,180]), declination [−90,90] |
HorizontalToEquatorialByLocalSiderealTime |
horizontal to equatorial with manual sidereal time | sidereal time in hours; RA ∈ [0,360) |
Horizontal |
horizontal result value | fields Azimuth, Altitude, Zenith, and HourAngle, all in degrees |
// Observer at 115°E, 40°N; eq comes from the shared preamble.
hz := coord.EquatorialToHorizontal(date, eq.RA, eq.Dec, 115, 40)
manual := coord.EquatorialToHorizontalByLocalSiderealTime(10.5, eq.RA, eq.Dec, 40)
hz2 := coord.HourAngleDeclinationToHorizontal(hz.HourAngle, eq.Dec, 40)
ha, dec := coord.HorizontalToHourAngleDeclination(hz2.Azimuth, hz2.Altitude, 40)
eq2 := coord.HorizontalToEquatorialByLocalSiderealTime(10.5, hz2.Azimuth, hz2.Altitude, 40)
fmt.Printf("auto=(%.6f, %.6f, %.6f) manual=(%.6f, %.6f)\n",
hz.Azimuth, hz.Altitude, hz.Zenith, manual.Azimuth, manual.Altitude)
fmt.Printf("roundtrip ha=%.6f dec=%.6f ra=%.6f\n", ha, dec, eq2.RA)
Topocentric Coordinates
| Name | Purpose | Units and conventions |
|---|---|---|
TopocentricEquatorial |
geocentric to topocentric equatorial | distanceAU is the geocentric distance in AU; height is the ellipsoidal height in meters; RA only receives a small parallax correction and is not normalized into 360° |
TopocentricEcliptic |
geocentric to topocentric ecliptic | same; it solves the topocentric equatorial position first and converts to the ecliptic, so Lon is normalized into [0,360) and Lat lands in [−90,90] |
Ecliptic / Equatorial |
return value types | same field conventions as the ecliptic ↔ equatorial group |
// Target 0.00257 AU from the geocenter (roughly the Moon), observer at 115°E, 40°N, ellipsoidal height 53 m.
top := coord.TopocentricEquatorial(date, eq.RA, eq.Dec, 115, 40, 0.00257, 53)
topEcl := coord.TopocentricEcliptic(date, 139.686111, 4.875278, 115, 40, 0.00257, 53)
fmt.Printf("top=(%.9f, %.9f) dRA=%.9f dDec=%.9f\n",
top.RA, top.Dec, top.RA-eq.RA, top.Dec-eq.Dec)
fmt.Printf("topEcl=(%.9f, %.9f)\n", topEcl.Lon, topEcl.Lat)
Topocentric geometry drives per-site occultation contact times, see Local event chart; topocentric quantities in sun and moon appear under Sun and Moon positions.
Sidereal Time
| Name | Purpose | Units and conventions |
|---|---|---|
MeanSiderealTime |
Greenwich mean sidereal time | hours; converts civil time to UT1 first, model is IAU 2006 (ERA route) |
ApparentSiderealTime |
Greenwich apparent sidereal time | hours; mean sidereal time plus the projection of the IAU 2000B longitude nutation |
HourAngle |
hour angle from apparent RA and site longitude | longitude east-positive, degrees; hour angle [0,360); based on apparent sidereal time |
// Local sidereal time = Greenwich sidereal time + east longitude/15, folded back into [0,24) hours; math is only for this step.
gmst := coord.MeanSiderealTime(date)
gast := coord.ApparentSiderealTime(date)
lst := math.Mod(gmst+115.0/15, 24)
fmt.Printf("GMST=%.9f h GAST=%.9f h LST=%.9f h\n", gmst, gast, lst)
fmt.Printf("HA=%.6f deg\n", coord.HourAngle(date, eq.RA, 115))
Precession and Nutation
| Name | Purpose | Units and conventions |
|---|---|---|
Precess |
precess equatorial coordinates from one date to another | RA/Dec in degrees; both dates are civil instants; pure precession rotation, no proper motion |
EclipticObliquity |
ecliptic obliquity | degrees; nutation=false gives the IAU 1980 mean obliquity, true adds the IAU 2000B obliquity nutation |
Nutation2000B |
IAU 2000B nutation | returns (longitude nutation, obliquity nutation) in degrees |
Nutation1980 |
IAU 1980 nutation | returns (longitude nutation, obliquity nutation) in degrees |
// Precess J2000 equatorial coordinates to date and list both nutation series for comparison.
j2000 := time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)
p := coord.Precess(j2000, date, 83.6331, 22.0145)
dLon2000B, dObl2000B := coord.Nutation2000B(date)
dLon1980, dObl1980 := coord.Nutation1980(date)
fmt.Printf("precessed=(%.9f, %.9f)\n", p.RA, p.Dec)
fmt.Printf("eps mean=%.9f true=%.9f\n",
coord.EclipticObliquity(date, false), coord.EclipticObliquity(date, true))
fmt.Printf("nut 2000B=(%.9f, %.9f) 1980=(%.9f, %.9f)\n",
dLon2000B, dObl2000B, dLon1980, dObl1980)
Galactic Coordinates
| Name | Purpose | Units and conventions |
|---|---|---|
EquatorialToGalactic |
ICRS equatorial to Galactic | ICRS input in degrees; Lon ∈ [0,360), Lat ∈ [−90,90]; fixed rotation matrix, no precession or proper motion |
GalacticToEquatorial |
Galactic to ICRS equatorial | transpose of the same matrix; RA ∈ [0,360) |
Galactic |
Galactic result value | fields Lon and Lat, in degrees |
// The Galactic-center direction in ICRS (266.4051, -28.936175) should come back near l=0, b=0.
gal := coord.EquatorialToGalactic(266.4051, -28.936175)
back := coord.GalacticToEquatorial(gal.Lon, gal.Lat)
fmt.Printf("gal=(%.6f, %.6f) back=(%.9f, %.9f)\n", gal.Lon, gal.Lat, back.RA, back.Dec)
Angular Separation
| Name | Purpose | Units and conventions |
|---|---|---|
AngularSeparation |
angular distance between two equatorial coordinates | inputs and output in degrees; great-circle angle, independent of input order |
// Angular distance between the Crab pulsar direction and the Galactic center.
sep := coord.AngularSeparation(83.6331, 22.0145, 266.4051, -28.936175)
fmt.Printf("sep=%.9f deg = %.6f arcsec\n", sep, sep*3600)
Atmospheric Refraction
| Name | Purpose | Units and conventions |
|---|---|---|
ApparentAltitude |
true altitude to apparent altitude | altitude and return value in degrees; pressure hPa, temperature °C |
TrueAltitude |
apparent altitude to true altitude | numerically inverts the Saemundsson model; returns NaN when there is no solution |
AtmosphericRefractionFromTrueAltitude |
refraction at a true altitude | degrees; add it to the true altitude to get the apparent altitude |
AtmosphericRefractionFromApparentAltitude |
refraction at an apparent altitude | degrees; subtract it from the apparent altitude to get the true altitude |
EquatorialToApparentHorizontal |
equatorial to apparent horizontal | applies refraction on top of EquatorialToHorizontal, changing only Altitude/Zenith, leaving Azimuth/HourAngle untouched |
// True altitude 10 deg, standard pressure 1010 hPa, temperature 0 C.
apparent := coord.ApparentAltitude(10, 1010, 0)
ref := coord.AtmosphericRefractionFromTrueAltitude(10, 1010, 0)
appHz := coord.EquatorialToApparentHorizontal(date, eq.RA, eq.Dec, 115, 40, 1010, 0)
fmt.Printf("true=10 apparent=%.9f ref=%.9f\n", apparent, ref)
fmt.Printf("back=%.9f appHz alt=%.6f zen=%.6f\n",
coord.TrueAltitude(apparent, 1010, 0), appHz.Altitude, appHz.Zenith)
Airmass
| Name | Purpose | Units and conventions |
|---|---|---|
AirmassPlaneParallelFromTrueAltitude |
plane-parallel model | true altitude in degrees; equivalent to sec(z), good at moderate altitude, diverges near the horizon |
AirmassKastenYoungFromApparentAltitude |
Kasten-Young 1989 | apparent altitude in degrees; no automatic refraction correction |
AirmassPickeringFromApparentAltitude |
Pickering 2002 | apparent altitude in degrees; aimed at low-altitude observations |
AirmassKastenYoungFromTrueAltitude |
refraction first, then Kasten-Young | true altitude in degrees; pressure hPa, temperature °C |
AirmassPickeringFromTrueAltitude |
refraction first, then Pickering | same |
// True altitude 10 deg, standard pressure 1010 hPa, temperature 0 C; FromTrueAltitude applies refraction first.
apparent := coord.ApparentAltitude(10, 1010, 0)
fmt.Printf("plane=%.9f\n", coord.AirmassPlaneParallelFromTrueAltitude(10))
fmt.Printf("ky true=%.9f apparent=%.9f\n",
coord.AirmassKastenYoungFromTrueAltitude(10, 1010, 0),
coord.AirmassKastenYoungFromApparentAltitude(apparent))
fmt.Printf("pickering true=%.9f apparent=%.9f\n",
coord.AirmassPickeringFromTrueAltitude(10, 1010, 0),
coord.AirmassPickeringFromApparentAltitude(apparent))
When only the raw formula is needed without the coordinate-layer refraction, use the three airmass models in formula.
Parallactic Angle
| Name | Purpose | Units and conventions |
|---|---|---|
ParallacticAngle |
parallactic angle from apparent RA/Dec | longitude/latitude in degrees; internally hour angle plus declination, returns (−180,180] |
ParallacticAngleByHourAngle |
parallactic angle from hour angle | hour angle, declination, latitude in degrees; returns (−180,180] |
// The parallactic angle drives camera rotation, spectrograph slit direction, and field orientation.
q := coord.ParallacticAngle(date, eq.RA, eq.Dec, 115, 40)
q2 := coord.ParallacticAngleByHourAngle(coord.HourAngle(date, eq.RA, 115), eq.Dec, 40)
fmt.Printf("q=%.9f qByHA=%.9f\n", q, q2)
The direction convention for the parallactic angle follows Observing-angle semantics.
Usage examples
Round-trip conversion between ecliptic and horizontal coordinates
eq := coord.EclipticToEquatorial(date, 139.686111, 4.875278)
hz := coord.EquatorialToHorizontal(date, eq.RA, eq.Dec, 115, 40)
back := coord.EquatorialToEcliptic(date, eq.RA, eq.Dec)
fmt.Println(eq.RA, eq.Dec, hz.Altitude, back.Lon)
143.72223158223719 19.53512536790277 -17.686511328302952 139.68611100000000
The instant and the coordinates are enough; the library converts with the apparent obliquity of that day.
Use the EclipticToEquatorialByObliquity / EquatorialToEclipticByObliquity research entry points to prescribe the obliquity yourself.
Round trips are self-consistent: EquatorialToEcliptic(EclipticToEquatorial(...)) returns the original values under one convention.
Topocentric correction and sidereal time
top := coord.TopocentricEquatorial(date, eq.RA, eq.Dec, 115, 40, 0.00257, 53)
fmt.Println(top.RA, top.Dec)
fmt.Println(coord.MeanSiderealTime(date), coord.ApparentSiderealTime(date))
// With a local sidereal time in hand you can skip the library's own computation:
manual := coord.EquatorialToHorizontalByLocalSiderealTime(10.5, 83.6331, 22.0145, 31.2)
fmt.Println(manual.Azimuth, manual.Altitude, manual.HourAngle)
144.25512626944376 18.79025523004319
16.852455700085 16.852556781127
281.869347 24.489608 73.8669
distanceAU must be a real geocentric distance (about 0.00257 AU for the Moon); whether parallax can be neglected for a more distant target depends on the required accuracy.
height is ellipsoidal height in metres and sidereal time is in hours.
Refraction, airmass and parallactic angle
fmt.Println(coord.ApparentAltitude(10, 1010, 0)) // true 10 -> apparent
fmt.Println(coord.TrueAltitude(10.093428, 1010, 0)) // apparent -> true (inverse)
fmt.Println(coord.AirmassKastenYoungFromTrueAltitude(10, 1010, 0)) // airmass at true altitude 10
fmt.Println(coord.ParallacticAngle(date, eq.RA, eq.Dec, 115, 40)) // camera/slit rotation angle
10.093427592862
10.000000410649
5.537933369472
-34.000957202636
Refraction only applies for true altitudes inside (-5, 90) degrees and contributes nothing outside; the airmass family also offers simplified entry points that take only an apparent altitude (...FromApparentAltitude).
Precession, nutation and obliquity conventions
fmt.Println(coord.EclipticObliquity(date, true)) // true obliquity; false gives the mean one
lon, obl := coord.Nutation2000B(date) // IAU 2000B longitude/obliquity nutation
pre := coord.Precess(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), date, eq.RA, eq.Dec)
fmt.Println(lon, obl, pre.RA, pre.Dec)
23.438261476424
0.001652570533 0.002392788113 144.089973153762 19.416731576422
Both Nutation1980 and Nutation2000B can be called explicitly for term-by-term comparison; Precess applies precession only, without proper motion, nutation or aberration.
Research entry points such as a manual sidereal time or a manual obliquity live under Research APIs and observing helpers.
Research APIs and Observing Helpers
Research-style coord helpers do not automatically substitute the current obliquity or sidereal time. They are useful for experiments with custom axial tilts or manually specified hour angles.
For ordinary observing calculations, use the time.Time based APIs such as EclipticToEquatorial and EquatorialToHorizontal.
Observing helpers:
ParallacticAngle/ParallacticAngleByHourAngle: parallactic angle, or the direction angle of the zenith at the targetAirmass...FromApparentAltitude: apply empirical airmass formula directly when apparent altitude is already knownAirmass...FromTrueAltitude: estimate refraction from pressure/temperature, convert true altitude to apparent altitude, then compute airmass
// Parallactic angle of the target, useful for camera rotation, spectrograph slit direction, and field orientation.
q := coord.ParallacticAngle(date, eq.RA, eq.Dec, 115, 40)
// With true altitude as input, estimate refraction first and then compute empirical airmass.
x := coord.AirmassKastenYoungFromTrueAltitude(10, 1010, 0)
fmt.Printf("q=%.6f airmass=%.6f\n", q, x)
The same observing helpers are also exposed in sun, moon, star, and the seven major-planet packages. If apparent altitude is already available and only the raw formula is needed, use formula.Airmass....
Parameter and result conventions
Units
-
Angles are always degrees: ecliptic longitude/latitude, right ascension/declination, azimuth/altitude/zenith distance/hour angle, parallactic angle, and angular separation.
Multiply by 3600 yourself when arcseconds are needed (for example the return value of
AngularSeparation). -
Sidereal time is always hours: the return values of
MeanSiderealTimeandApparentSiderealTime, and thelocalSiderealTimeHoursargument of every*ByLocalSiderealTimeentry point, are all hours; internally they are multiplied by 15 to become degrees, so do not confuse them with the hour notation of right ascension. -
Equatorial.RA,Galactic.Lon,Horizontal.Azimuth, andHorizontal.HourAngleare all degrees, not hours; theEquatorialtype does not distinguish J2000, mean-of-date, or apparent coordinates, so the caller must keep the input convention consistent. -
Distances are AU (the
distanceAUargument ofTopocentricEquatorialandTopocentricEcliptic); observer height is the ellipsoidal height (geodetic height) in meters, with no geoid modeling, see the manual Observer height conventions. -
Pressure is hPa and temperature is °C; refraction is calibrated at the standard state of
1010 hPaand10 °C. -
This package produces no apparent diameter or apparent radius. The apparent-radius fields in the Sun/Moon and eclipse manuals are in arcseconds; do not interchange them with the degrees used here.
Time Scales
-
Every public entry point treats
time.Timeas an absolute (civil) instant and converts it withdate.UTC()before computing the Julian date; the value itself is always interpreted as a UTC label, and equals UT1 before1972-01-01.The full convention is in the manual Time scale conventions.
-
The sidereal-time chain converts to UT1 explicitly:
MeanSiderealTime/ApparentSiderealTimerun the IAU 2006 model afterUTC2UT1, and the apparent sidereal time insideHourAngle,EquatorialToHorizontal, andTopocentricEquatorialis UT1-based as well. -
Both
fromandtoofPrecess(from, to)are civil instants, each supplying its own precession epoch. -
Refraction and airmass do not involve time at all; they depend only on altitude and meteorological parameters.
Angle Quadrants and Normalization
- The internal
normalize360normalizes every angle that is meant to be displayed into[0,360): right ascension, ecliptic longitude, Galactic longitude, azimuth, and hour angle all land in that interval. - Declination, ecliptic latitude, Galactic latitude, and altitude pass through
Asin(with an extra[−1,1]clamp on some paths) and land in[−90,90]. ParallacticAngle/ParallacticAngleByHourAngleuseAtan2and return(−180,180]; this is the only signed angle in the package.- The topocentric family is the exception:
TopocentricEquatorial.RAis the input RA plus a small correction and is not normalized into 360°, so it may cross the boundary slightly when the target is near0°/360°;TopocentricEclipticnormalizesLoninto[0,360)and returnsLatthroughAsininside[−90,90], because it solves the topocentric equatorial position first and converts to the ecliptic afterwards. - The hour angle returned by
HourAngleDeclinationToHorizontalandHorizontalToHourAngleDeclinationis normalized into[0,360), so a negative hour angle west of the meridian appears here as360−|HA|; subtract 360 yourself when a signed hour angle is needed. EquatorialToHorizontalandHourAngleDeclinationToHorizontalboth return a geometric altitude without refraction; useEquatorialToApparentHorizontalorApparentAltitudefor an apparent altitude.
Zero Values and Invalid Input
- The refraction family requires
pressureHPa > 0andtemperatureC > −273.15; any NaN/Inf or out-of-range parameter returns NaN. - At a true altitude
≤ −5°or≥ 90°the refraction is treated as 0:ApparentAltitudereturns the true altitude unchanged, andTrueAltitudeandAtmosphericRefractionFromApparentAltitudereturn the input apparent altitude unchanged;TrueAltitudeonly solves numerically inside(−5°, 90°)and returns NaN when the inversion fails. - The airmass family limits altitude (or zenith distance) to
[0,90]and returns NaN outside or for non-finite input; the plane-parallel model returns+Infexactly at altitude0°(zenith distance90°), while Kasten-Young and Pickering stay finite at0°. EquatorialToApparentHorizontalpasses pressure and temperature straight to the refraction function: invalid meteorological parameters turnAltitudeand thereforeZenithinto NaN, whileAzimuthandHourAngleremain geometric values.- The topocentric family does not validate
distanceAUorheight; passing0or a negativedistanceAUyields meaningless results, so pass a real geocentric distance (about0.00257 AUfor the Moon). - Purely geometric entry points such as the Galactic conversions and the angular separation do not validate parameters: non-finite input propagates to NaN, and an ecliptic latitude outside
[−90,90]is not clamped but reinterpreted as a spherical direction. - The four coordinate types have no
Validfield:Ecliptic{},Equatorial{},Horizontal{}, andGalactic{}mean "all angles zero", not "unset", so zero values are not usable as an unset sentinel.
Accuracy and Applicability
- Ecliptic obliquity: the mean term uses the IAU 1980 model, and
EclipticObliquity(date, true)adds the IAU 2000B obliquity nutation on top; bothNutation1980andNutation2000Bare available and can be called explicitly for term-by-term comparison. - Sidereal time: the IAU 2006 ERA route, with apparent sidereal time adding the IAU 2000B longitude nutation; apparent sidereal time for the same Julian date is memoized in a bounded table, and an injected ΔT override invalidates the memo by generation.
- Precession:
Precessapplies a long-term precession rotation to equatorial coordinates (its equatorial/ecliptic pole vectors include periodic terms), without proper motion, nutation, or aberration; it moves mean coordinates from one epoch to another and cannot replace a full "J2000 to apparent-of-date" chain. - Galactic: a fixed ICRS ↔ Galactic rotation matrix, equivalent to SOFA
iauIcrs2g/iauG2icrs, valid only for ICRS-convention input. - Topocentric correction: the diurnal parallax scales inversely with
distanceAUand is largest for the Moon (about 1°), and whether it can be neglected for a more distant body depends on the required accuracy;heightonly scales the parallax term, with meter-level elevation producing arcsecond-level differences. - Topocentric ecliptic coordinates come from the topocentric equatorial position (
basic.TopocentricLoBoreturns both from a single evaluation):TopocentricEcliptic.Latis guaranteed to land in[−90,90]and agrees with the independentEquatorialToEcliptic(TopocentricEquatorial(...))route. - Refraction: the Saemundsson formula, valid for true altitudes in
(−5°, 90°), with no correction outside that window; refraction changes rapidly at low altitude (< 5°), where ±1 hPa or ±1 °C already produces a visible error. - Airmass: the three empirical models agree at moderate altitude and differ most near the horizon; the plane-parallel model is a purely geometric approximation that diverges near the horizon, so use Kasten-Young or Pickering for careful low-altitude estimates, with the raw formulas in
formulaunder airmass models. - Parallactic angle:
ParallacticAngleuses apparent RA/Dec and the site longitude/latitude, whileParallacticAngleByHourAngleuses the hour-angle form of the same geometry; both must agree for the same geometry.
Observer height
Topocentric coordinates, rise/set, local eclipses and occultations interpret observer height as ellipsoidal height in metres. Convert orthometric height H using geoid undulation N: height = H + N.
The library does not include a geoid model. height=0 therefore denotes the reference ellipsoid, rather than local mean sea level.
Obtain N from an external model when that difference matters, and match the observer-height convention when comparing external predictions.
For scale, a height difference of 30 m projects to roughly 52 m on the ground along a ray at 30° altitude, using |Δh|·cot(altitude). Its effect on a contact time depends on the local shadow velocity and contact geometry; it is not a fixed time correction.