16c62a97d5
- 新增时标、ΔT 模型、质心时间与 UT1 支持 - 改进日月食、月掩、行星事件及路径边界计算 - 完善恒星三维自行与动态距离传播 - 扩展 SVG、GeoJSON、KML 输出与底层距离换算工具 - 整理中英文手册、示例资源及回归测试
425 lines
28 KiB
Markdown
425 lines
28 KiB
Markdown
# Coordinate Tools
|
||
|
||
[中文](../coord.md) | [Back to README](../../../README.en.md)
|
||
|
||
`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](sun-moon.md#observing-angle-semantics); full topocentric usage appears in [Occultation · Local event chart](occultation.md#fixed-site-charts) and [Sun and Moon positions](sun-moon.md#sun-and-moon-position);
|
||
|
||
topocentric solar-eclipse contacts appear in [Solar eclipses](eclipse.md#solar-eclipse).
|
||
|
||
Time scales, observer height, and overall accuracy are documented in [Time scale conventions](timescale.md), [Observer height conventions](coord.md#observer-height), and [Applicability and accuracy](accuracy.md).
|
||
|
||
## Contents
|
||
|
||
- [Converting ecliptic coordinates to equatorial and horizontal](#converting-ecliptic-coordinates-to-equatorial-and-horizontal)
|
||
- [API Reference](#api-reference)
|
||
- [Ecliptic ↔ Equatorial](#ecliptic--equatorial)
|
||
- [Equatorial ↔ Horizontal](#equatorial--horizontal)
|
||
- [Topocentric Coordinates](#topocentric-coordinates)
|
||
- [Sidereal Time](#sidereal-time)
|
||
- [Precession and Nutation](#precession-and-nutation)
|
||
- [Galactic Coordinates](#galactic-coordinates)
|
||
- [Angular Separation](#angular-separation)
|
||
- [Atmospheric Refraction](#atmospheric-refraction)
|
||
- [Airmass](#airmass)
|
||
- [Parallactic Angle](#parallactic-angle)
|
||
- [Usage examples](#usage-examples)
|
||
- [Round-trip conversion between ecliptic and horizontal coordinates](#round-trip-conversion-between-ecliptic-and-horizontal-coordinates)
|
||
- [Topocentric correction and sidereal time](#topocentric-correction-and-sidereal-time)
|
||
- [Refraction, airmass and parallactic angle](#refraction-airmass-and-parallactic-angle)
|
||
- [Precession, nutation and obliquity conventions](#precession-nutation-and-obliquity-conventions)
|
||
- [Research APIs and Observing Helpers](#research-apis-and-observing-helpers)
|
||
- [Parameter and result conventions](#parameter-and-result-conventions)
|
||
- [Units](#units)
|
||
- [Time Scales](#time-scales)
|
||
- [Angle Quadrants and Normalization](#angle-quadrants-and-normalization)
|
||
- [Zero Values and Invalid Input](#zero-values-and-invalid-input)
|
||
- [Accuracy and Applicability](#accuracy-and-applicability)
|
||
- [Observer height](#observer-height)
|
||
|
||
## Converting ecliptic coordinates to equatorial and horizontal
|
||
|
||
```go
|
||
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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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](occultation.md#fixed-site-charts); topocentric quantities in `sun` and `moon` appear under [Sun and Moon positions](sun-moon.md#sun-and-moon-position).
|
||
|
||
### 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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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 |
|
||
|
||
```go
|
||
// 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](formula.md#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]` |
|
||
|
||
```go
|
||
// 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](sun-moon.md#observing-angle-semantics).
|
||
|
||
## Usage examples
|
||
|
||
### Round-trip conversion between ecliptic and horizontal coordinates
|
||
|
||
```go
|
||
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)
|
||
```
|
||
|
||
```text
|
||
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
|
||
|
||
```go
|
||
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)
|
||
```
|
||
|
||
```text
|
||
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
|
||
|
||
```go
|
||
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
|
||
```
|
||
|
||
```text
|
||
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
|
||
|
||
```go
|
||
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)
|
||
```
|
||
|
||
```text
|
||
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 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 target
|
||
- `Airmass...FromApparentAltitude`: apply empirical airmass formula directly when apparent altitude is already known
|
||
- `Airmass...FromTrueAltitude`: estimate refraction from pressure/temperature, convert true altitude to apparent altitude, then compute airmass
|
||
|
||
```go
|
||
// 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 `MeanSiderealTime` and `ApparentSiderealTime`, and the `localSiderealTimeHours` argument of every `*ByLocalSiderealTime` entry 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`, and `Horizontal.HourAngle` are all **degrees**, not hours; the `Equatorial` type does not distinguish J2000, mean-of-date, or apparent coordinates, so the caller must keep the input convention consistent.
|
||
- Distances are AU (the `distanceAU` argument of `TopocentricEquatorial` and `TopocentricEcliptic`); observer height is the **ellipsoidal height (geodetic height)** in meters, with no geoid modeling, see the manual [Observer height conventions](coord.md#observer-height).
|
||
- Pressure is hPa and temperature is °C; refraction is calibrated at the standard state of `1010 hPa` and `10 °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.Time` as an absolute (civil) instant and converts it with `date.UTC()` before computing the Julian date; the value itself is always interpreted as a UTC label, and equals UT1 before `1972-01-01`.
|
||
|
||
The full convention is in the manual [Time scale conventions](timescale.md).
|
||
- The sidereal-time chain converts to UT1 explicitly: `MeanSiderealTime` / `ApparentSiderealTime` run the IAU 2006 model after `UTC2UT1`, and the apparent sidereal time inside `HourAngle`, `EquatorialToHorizontal`, and `TopocentricEquatorial` is UT1-based as well.
|
||
- Both `from` and `to` of `Precess(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 `normalize360` normalizes 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` / `ParallacticAngleByHourAngle` use `Atan2` and return `(−180,180]`; this is the only signed angle in the package.
|
||
- The topocentric family is the exception: `TopocentricEquatorial.RA` is the input RA plus a small correction and is not normalized into 360°, so it may cross the boundary slightly when the target is near `0°/360°`; `TopocentricEcliptic` normalizes `Lon` into `[0,360)` and returns `Lat` through `Asin` inside `[−90,90]`, because it solves the topocentric equatorial position first and converts to the ecliptic afterwards.
|
||
- The hour angle returned by `HourAngleDeclinationToHorizontal` and `HorizontalToHourAngleDeclination` is normalized into `[0,360)`, so a negative hour angle west of the meridian appears here as `360−|HA|`; subtract 360 yourself when a signed hour angle is needed.
|
||
- `EquatorialToHorizontal` and `HourAngleDeclinationToHorizontal` both return a **geometric** altitude without refraction; use `EquatorialToApparentHorizontal` or `ApparentAltitude` for an apparent altitude.
|
||
|
||
### Zero Values and Invalid Input
|
||
|
||
- The refraction family requires `pressureHPa > 0` and `temperatureC > −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: `ApparentAltitude` returns the true altitude unchanged, and `TrueAltitude` and `AtmosphericRefractionFromApparentAltitude` return the input apparent altitude unchanged; `TrueAltitude` only 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 `+Inf` exactly at altitude `0°` (zenith distance `90°`), while Kasten-Young and Pickering stay finite at `0°`.
|
||
- `EquatorialToApparentHorizontal` passes pressure and temperature straight to the refraction function: invalid meteorological parameters turn `Altitude` and therefore `Zenith` into NaN, while `Azimuth` and `HourAngle` remain geometric values.
|
||
- The topocentric family does not validate `distanceAU` or `height`; passing `0` or a negative `distanceAU` yields meaningless results, so pass a real geocentric distance (about `0.00257 AU` for 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 `Valid` field: `Ecliptic{}`, `Equatorial{}`, `Horizontal{}`, and `Galactic{}` 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; both `Nutation1980` and `Nutation2000B` are 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: `Precess` applies 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 `distanceAU` and is largest for the Moon (about 1°), and whether it can be neglected for a more distant body depends on the required accuracy; `height` only scales the parallax term, with meter-level elevation producing arcsecond-level differences.
|
||
- Topocentric ecliptic coordinates come from the topocentric equatorial position (`basic.TopocentricLoBo` returns both from a single evaluation): `TopocentricEcliptic.Lat` is guaranteed to land in `[−90,90]` and agrees with the independent `EquatorialToEcliptic(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 `formula` under [airmass models](formula.md#airmass-models).
|
||
- Parallactic angle: `ParallacticAngle` uses apparent RA/Dec and the site longitude/latitude, while `ParallacticAngleByHourAngle` uses 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.
|