feat: 引入可切换星历模型并扩展天象观测链路
- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算 - 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询 - 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出 - 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解 - 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤 - 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持 - 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理 - 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
+134
-75
@@ -1,99 +1,158 @@
|
||||
# Accuracy and performance
|
||||
# Accuracy and Performance
|
||||
|
||||
[中文](../accuracy.md) | [README](../../../README.en.md)
|
||||
[中文](../accuracy.md) | [Back to README](../../../README.en.md)
|
||||
|
||||
These tables describe the built-in models and previously measured comparisons. A sampled maximum is not an error bound for every date or site.
|
||||
|
||||
External comparisons require matching the time scale, coordinate frame, observer height and refraction model.
|
||||
|
||||
See [Time scales](timescale.md) for UTC, UT1 and TT.
|
||||
Two ephemerides are built in, and their errors vary with model, epoch and calculated quantity. Values do not apply outside the model and range stated in each table header.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Sun and planets](#sun-and-planets)
|
||||
- [Moon](#moon)
|
||||
- [Ephemeris models](#ephemeris-models)
|
||||
- [Model selection](#model-selection)
|
||||
- [Measurement conventions](#measurement-conventions)
|
||||
- [Geocentric apparent direction](#geocentric-apparent-direction)
|
||||
- [Fixed-ICRF geometric directions](#fixed-icrf-geometric-directions)
|
||||
- [Geometric lunar distance](#geometric-lunar-distance)
|
||||
- [Legacy truncation error](#legacy-truncation-error)
|
||||
- [Lite lightweight chains](#lite-lightweight-chains)
|
||||
- [Accuracy references](#accuracy-references)
|
||||
- [Performance](#performance)
|
||||
- [Conversions and caveats](#conversions-and-caveats)
|
||||
|
||||
## Sun and planets
|
||||
## Ephemeris models
|
||||
|
||||
The Sun and planets use built-in VSOP87 analytical terms. The current table entries cover roughly 4000 years around J2000. The table below lists truncation errors relative to the complete VSOP87 tables:
|
||||
The library ships two ephemerides; the default is a **semianalytic model** (`ModelSemianalytic`): it keeps the argument structure (integer multipliers) and initial amplitudes of the planetary theories, then refits the amplitudes, fundamental-argument polynomials and a small number of correction terms to DE441, folding everything into a single series table. No residual layer is attached at runtime and no external ephemeris file is needed.
|
||||
|
||||
- **Base theories**: VSOP2013 for Mercury, Venus, Earth and Mars; TOP2013 for Jupiter, Saturn, Uranus and Neptune; ELP/MPP02 for the Moon.
|
||||
- **Fitting truth**: DE441 for the long range; DE440 for the planetary modern segment.
|
||||
- **Coverage**: TT JD `-1931455.0 … 5373545.0` (12000 Julian years before and 8000 after J2000); values outside it are extrapolated with no accuracy guarantee.
|
||||
- **Modern segment** (planets only): a separate table fitted over J2000 ±1000 Julian years (TT JD `2086295.0 … 2816795.0`) against DE440, blended with the long-range table over 100 years at each end; use it for refinement at recent epochs.
|
||||
- **Evaluation scale**: `n < 0` uses the full series, matching the APIs without an `N`; every `n >= 0` shares one fast table intended for coarse searches. Only Legacy truncates its series according to `n`.
|
||||
- **Data size**: the planetary table is about 452 KiB and the lunar table about 42 KiB, compiled into the program; both are linked even when only some bodies are used, and explicit instances do not reduce the binary size.
|
||||
- **Frame**: the tables hold geometric positions in the J2000 mean ecliptic; at evaluation they are rotated to the mean ecliptic of date and then follow the same apparent-place chain as Legacy (light time, aberration, precession and nutation).
|
||||
|
||||
`ModelLegacy` uses truncated VSOP87/ELP2000-82 series with correction terms added on top: a cubic polynomial for Earth and constant offsets for the other planets, and for the Moon an extra polynomial, a precession term and a fit for 3000–5000 CE. `lite/sun` and `lite/moon` use a different set of simplified formulas; do not mix the two.
|
||||
|
||||
## Model selection
|
||||
|
||||
| Model | Content | Use |
|
||||
| --- | --- | --- |
|
||||
| `ModelSemianalytic` | Built-in semianalytic tables, the model in default builds (without the `astro_legacy` tag) | Everyday positions, calendars, observing and event calculations |
|
||||
| `ModelLegacy` | Truncated VSOP87/ELP2000-82 series with long-term and per-body corrections, `n` truncating by term count | Reproducing old values and comparing models; the default under an `astro_legacy` build |
|
||||
| `ModelCustom` | SPK or a caller-supplied read-only engine | Accuracy determined jointly by the external kernel, its usable interval and the upper-level transforms |
|
||||
| `lite/sun`, `lite/moon` | Independent approximation chains | Resource-constrained environments; do not follow model switches |
|
||||
|
||||
An explicit `Context`/`Instance` fixes the ephemeris, but ΔT, TT−UTC and civil time-scale policy remain process-wide settings. Signatures are unchanged while default values may change, which differs from older versions; select Legacy explicitly when old model values are needed. See [Explicit ephemeris instances](ephemeris.md).
|
||||
|
||||
## Measurement conventions
|
||||
|
||||
Unless stated otherwise, the data below are comparisons against DE440/DE441 on TT grid points, in arcseconds:
|
||||
|
||||
- The reference side uses Skyfield 1.55 and converts only TT to TDB, introducing no UTC, UT1 or ΔT differences. "Semianalytic full" in the tables means `n < 0`, and "semianalytic fast" means `n >= 0`.
|
||||
- Modern range: TT JD `2415020.5 … 2488434.25`, step 0.25 day, `293656` points, approximately 1900–2100.
|
||||
- Long range: TT JD `990545.0 … 3912545.0`, J2000 ±4000 Julian years, `365252` points; the two DE441 kernel segments switch at JD `2440416.5`.
|
||||
- Geometric quantities use the mean ecliptic/equinox of date; apparent RA/Dec use the true equator/equinox of date.
|
||||
- In the tables MAE is the mean absolute error, RMS the root-mean-square error, P95 the 95th percentile of the absolute error and Max the sampled maximum; angles are geocentric apparent direction angles, not errors in a single coordinate component. The maxima are sampled maxima, not continuous-time error bounds.
|
||||
|
||||
## Geocentric apparent direction
|
||||
|
||||
Sun:
|
||||
|
||||
| Range / model | MAE | RMS | P95 | Max |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Modern DE440 / Legacy | `0.03895` | `0.04475` | `0.08013` | `0.1372` |
|
||||
| Modern DE440 / semianalytic full | `0.00556` | `0.00682` | `0.01329` | `0.02680` |
|
||||
| Modern DE440 / semianalytic fast | `0.1783` | `0.2219` | `0.4415` | `0.9675` |
|
||||
| Long DE441 / Legacy | `2.659` | `4.171` | `9.751` | `16.68` |
|
||||
| Long DE441 / semianalytic full | `1.774` | `3.187` | `7.908` | `14.08` |
|
||||
| Long DE441 / semianalytic fast | `2.431` | `4.050` | `9.878` | `17.15` |
|
||||
|
||||
Moon:
|
||||
|
||||
| Range / model | MAE | RMS | P95 | Max |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Modern DE440 / Legacy | `0.7431` | `0.7499` | `0.9171` | `1.3297` |
|
||||
| Modern DE440 / semianalytic full | `0.06302` | `0.07591` | `0.1437` | `0.4418` |
|
||||
| Modern DE440 / semianalytic fast | `3.455` | `3.991` | `7.092` | `18.18` |
|
||||
| Long DE441 / Legacy | `42.16` | `67.10` | `156.35` | `233.58` |
|
||||
| Long DE441 / semianalytic full | `1.788` | `3.182` | `7.920` | `14.08` |
|
||||
| Long DE441 / semianalytic fast | `6.098` | `7.359` | `14.30` | `39.74` |
|
||||
|
||||
In the modern samples the semianalytic full series has an apparent-direction RMS of `0.00682″` (Sun) and `0.07591″` (Moon), below the Legacy `0.04475″` and `0.7499″`; the fast table gives `0.2219″` and `3.991″`, worse than Legacy, especially for the Moon. Long-range results include the whole upper-level transformation chain and reference-frame differences, so they cannot be attributed entirely to the series themselves.
|
||||
|
||||
## Fixed-ICRF geometric directions
|
||||
|
||||
The following table compares the semianalytic full `Instance.State(target, BodyEarth, jd)` against DE geocentric ICRF geometric vectors directly, without date precession/nutation transforms. The library approximates TDB numerically with TT, and the reference also converts the same TT to TDB, so this set is a comparison under approximate TDB. Values are arcseconds:
|
||||
|
||||
| Range / body | MAE | RMS | P95 | Sample maximum |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Modern DE440 / Sun | `0.00551` | `0.00677` | `0.01320` | `0.02655` |
|
||||
| Modern DE440 / Moon | `0.06301` | `0.07591` | `0.1437` | `0.4414` |
|
||||
| Long DE441 / Sun | `0.04100` | `0.05754` | `0.1214` | `0.3613` |
|
||||
| Long DE441 / Moon | `0.09163` | `0.1129` | `0.2170` | `0.8825` |
|
||||
|
||||
In the long range the fixed-frame errors are much smaller than the upper-level apparent date-frame differences, showing that the error of the whole calculation chain cannot be summarized by a single "model accuracy" number. Legacy has no geometric engine and is not listed in this table.
|
||||
|
||||
## Geometric lunar distance
|
||||
|
||||
Values are km, as geocentric distances:
|
||||
|
||||
| Range / model | MAE | P95 | Max |
|
||||
| --- | --- | --- | --- |
|
||||
| Modern DE440 / Legacy | `0.1434` | `0.3633` | `0.9143` |
|
||||
| Modern DE440 / semianalytic full | `0.04674` | `0.1172` | `0.3312` |
|
||||
| Modern DE440 / semianalytic fast | `1.730` | `4.356` | `9.818` |
|
||||
| Long DE441 / Legacy | `2.204` | `9.834` | `26.90` |
|
||||
| Long DE441 / semianalytic full | `0.06496` | `0.1669` | `0.5576` |
|
||||
| Long DE441 / semianalytic fast | `2.005` | `5.160` | `16.10` |
|
||||
|
||||
## Legacy truncation error
|
||||
|
||||
The table below is a truncation-error reference for Legacy against complete VSOP87; it is neither semianalytic accuracy nor total error against DE440/DE441:
|
||||
|
||||
| Target | Longitude / latitude | Distance |
|
||||
| --- | --- | --- |
|
||||
| Sun / Earth | about `0.1"` | about `0.1 x 10^-6 AU` |
|
||||
| Mercury, Venus | about `0.2"` | about `0.2 x 10^-6 AU` |
|
||||
| Mars | about `0.5"` | about `1 x 10^-6 AU` |
|
||||
| Jupiter | about `0.5"` | about `3 x 10^-6 AU` |
|
||||
| Saturn | about `0.5"` | about `5 x 10^-6 AU` |
|
||||
| Uranus | about `1"` | about `20 x 10^-6 AU` |
|
||||
| Neptune | about `1"` | about `40 x 10^-6 AU` |
|
||||
|
||||
This is suitable for ordinary calendrical work, observing support, outreach, and personal research; spacecraft navigation, precise occultation prediction, and strict dynamical integration fall outside that range and usually need a professional ephemeris such as JPL DE.
|
||||
|
||||
## Moon
|
||||
|
||||
The Moon uses a built-in truncated ELP2000/82-style analytical series. The package stays lightweight and does not require external ephemeris files.
|
||||
|
||||
It is suitable for Chinese-calendar new moons, lunar phases, rise/set, lunar eclipses, amateur occultation prediction, and ordinary positional work; extremely high-precision lunar laser ranging, long-term physical libration, and professional occultation work fall outside that range and are best served by JPL or a dedicated lunar ephemeris.
|
||||
| Sun / Earth | about `0.1″` | about `0.1 × 10^-6 AU` |
|
||||
| Mercury, Venus | about `0.2″` | about `0.2 × 10^-6 AU` |
|
||||
| Mars | about `0.5″` | about `1 × 10^-6 AU` |
|
||||
| Jupiter | about `0.5″` | about `3 × 10^-6 AU` |
|
||||
| Saturn | about `0.5″` | about `5 × 10^-6 AU` |
|
||||
| Uranus | about `1″` | about `20 × 10^-6 AU` |
|
||||
| Neptune | about `1″` | about `40 × 10^-6 AU` |
|
||||
|
||||
## Lite lightweight chains
|
||||
|
||||
`lite/sun` and `lite/moon` are independent approximation chains. They do not depend on the VSOP87 or ELP2000/82 series used by `sun` / `moon`, and are intended for CPU- or memory-constrained environments.
|
||||
`lite/sun` uses simplified solar formulas and `lite/moon` an approximate lunar chain with a small number of perturbation terms; their rise/set searches use a fixed-step scan plus bisection, 30 minutes for the Sun and 15 minutes for the Moon. Neither uses the main-chain tables and neither follows model switches. The following are errors over all of 2026 at 8 sites relative to the Legacy main chain (statistics as in [Measurement conventions](#measurement-conventions)):
|
||||
|
||||
- `lite/sun`: simplified true/apparent solar longitude formulas plus lightweight equatorial conversion
|
||||
- `lite/moon`: Schlyter-style lunar approximation with about 15 perturbation terms plus lightweight topocentric correction
|
||||
- rise/set search: fixed-step scanning plus bisection, without the high-precision nutation iteration used by the main chain
|
||||
- zero heap allocation in the computation path (0 allocs/op); against the main chain, pure evaluation entry points such as position and phase run about `7.5-27.1x` faster, and rise/set entry points about `0.9-3.5x`
|
||||
|
||||
Capability boundaries:
|
||||
|
||||
| Package | Position model | Rise/set search | Main use |
|
||||
| Quantity | Mean absolute error | P95 | Max |
|
||||
| --- | --- | --- | --- |
|
||||
| `lite/sun` | simplified true/apparent solar longitude plus lightweight equatorial conversion | `30 min` scan plus bisection | sunrise/sunset, solar altitude, watch faces, frontend refresh loops |
|
||||
| `lite/moon` | Schlyter / vFPS lunar approximation plus lightweight topocentric correction | `15 min` scan plus bisection | moonrise/moonset, lunar phase, lunar age, lightweight lunar observing helpers |
|
||||
| Sunrise / sunset | `0.02 / 0.02 min` | `0.04 / 0.06 min` | `0.31 / 0.35 min` |
|
||||
| Moonrise / moonset | `0.28 / 0.36 min` | `0.57 / 0.86 min` | `1.44 / 1.24 min` |
|
||||
| `Phase()` | `0.00089` | `0.00185` | `0.00243` |
|
||||
| `PhaseAge()` | `0.003 d` | `0.010 d` | `0.014 d` |
|
||||
|
||||
Error against the `sun` / `moon` packages (year 2026, 8 observing sites; rise/set sampled every 7 or 15 days, phase/age every 6 hours):
|
||||
These samples are not all semantically identical in their events: sunsets have 2 high-latitude date-attribution differences across midnight and moonset has 1 high-latitude disagreement about whether the event occurs that day. Rise/set are sampled every 7 or 15 days and phase/age every 6 hours. The time differences in the table only indicate agreement for events that do occur; they cannot be used to decide whether an event exists. Eclipses, physical libration and high-latitude event boundaries are computed by the main chain, not by the lite chains.
|
||||
|
||||
| Capability | Mean absolute error | P95 | Max absolute error | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `lite/sun` sunrise | `0.02 min` | `0.04 min` | `0.31 min` | no event-existence mismatch in the sample set |
|
||||
| `lite/sun` sunset | `0.02 min` | `0.06 min` | `0.35 min` | `2` high-latitude samples differ only in day-attribution semantics across midnight |
|
||||
| `lite/moon` moonrise | `0.28 min` | `0.57 min` | `1.44 min` | no event-existence mismatch in the sample set |
|
||||
| `lite/moon` moonset | `0.36 min` | `0.86 min` | `1.24 min` | `1` high-latitude sample differs on whether the moonset belongs to the same civil day |
|
||||
| `lite/moon` `Phase()` | `0.00089` | `0.00185` | `0.00243` | compared with `moon.Phase` |
|
||||
| `lite/moon` `PhaseAge()` | `0.003 d` | `0.010 d` | `0.014 d` | about 4.3 min mean, 14.4 min P95, 20.2 min max |
|
||||
| `lite/moon` geocentric longitude | `2.41'` | `6.82'` | `9.91'` | relative to the main lunar chain |
|
||||
| `lite/moon` geocentric latitude | `0.87'` | `1.83'` | `2.92'` | relative to the main lunar chain |
|
||||
## Performance
|
||||
|
||||
`Go testing.Benchmark` reference values (single-machine measurements for comparison; absolute values vary with hardware):
|
||||
Linux/WSL, Intel i7-12700, Go 1.25.9, benchmark concurrency 20, 300 ms per benchmark. Calls repeat the same instant and site, so these are warm-cache measurements and exclude first-load cost:
|
||||
|
||||
| Entry point | Main chain | `lite` | Speedup | Main-chain allocation | `lite` allocation |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `Sun ApparentRaDec` | `6.031 µs/op` | `222.4 ns/op` | `27.1x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| `Sun Altitude` | `6.127 µs/op` | `672.3 ns/op` | `9.1x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| `Sun RiseTime` | `101.874 µs/op` | `29.168 µs/op` | `3.5x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| `Moon ApparentRaDec` | `16.897 µs/op` | `1.070 µs/op` | `15.8x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| `Moon Phase` | `15.441 µs/op` | `935.6 ns/op` | `16.5x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| `Moon Altitude` | `9.714 µs/op` | `1.294 µs/op` | `7.5x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| `Moon RiseTime` | `121.772 µs/op` | `132.312 µs/op` | `0.9x` | `0 B/op, 0 allocs/op` | `0 B/op, 0 allocs/op` |
|
||||
| Entry point | Semianalytic full | Legacy full | Lite |
|
||||
| --- | --- | --- | --- |
|
||||
| Sun `ApparentRaDec` | `0.606 µs` | `8.988 µs` | `0.213 µs` |
|
||||
| Sun `Altitude` | `0.770 µs` | `9.134 µs` | `0.627 µs` |
|
||||
| Sun `RiseTime` | `628.6 µs` | `144.8 µs` | `25.88 µs` |
|
||||
| Moon `ApparentRaDec` | `0.855 µs` | `9.373 µs` | `1.003 µs` |
|
||||
| Moon `Phase` | `1.171 µs` | `14.81 µs` | `0.914 µs` |
|
||||
| Moon `Altitude` | `1.006 µs` | `9.520 µs` | `1.136 µs` |
|
||||
| Moon `RiseTime` | `487.4 µs` | `135.0 µs` | `117.4 µs` |
|
||||
|
||||
The main-chain/`lite` gap depends on the scenario: pure evaluation entry points (position, phase) run about `7.5-27.1x` faster in `lite`, while rise/set entry points narrow to `0.9-3.5x` because both sides perform a time search; `Moon RiseTime` is close to parity.
|
||||
The warm-cache semianalytic position APIs are faster than Lite in some cases and slower in others; rise/set searches evaluate at a new epoch each time and therefore run slower, while Legacy is in fact faster for some rise/set entry points. These are absolute single-run values and vary with machine and call shape.
|
||||
|
||||
Use the main `sun` / `moon` chains for eclipses, physical libration, or high-latitude edge cases.
|
||||
Semianalytic Sun/Moon `RiseTime` allocate 60/36 times respectively, and the remaining evaluation APIs in the table allocate 0 times. Zero allocations cover only these paths and are not guaranteed for other APIs.
|
||||
|
||||
## Accuracy references
|
||||
## Conversions and caveats
|
||||
|
||||
The following entry points have been checked against JPL Horizons, NASA GSFC, and other public references; use them to judge the order of magnitude to expect:
|
||||
|
||||
- apparent diameters of the Sun, planets, and Moon: maximum differences from the external baseline range from `0.000002"` to `0.194598"` depending on the body; the Moon is the most sensitive because of parallax and distance changes
|
||||
- solar physical ephemerides `P/B0/L0`: maximum differences are about `0.003349° / 0.003986° / 0.047394°`
|
||||
- planetary rise, transit, and set: checked against JPL Horizons rise/transit/set events; that baseline is generated at a 1-minute step, and current results align with the Horizons event times at the minute level
|
||||
- Moon rise/set: `aero=true` uses dynamic standard refraction and the instantaneous lunar semidiameter for an upper-limb crossing.
|
||||
|
||||
Across 14 sea-level events at 7 sites, the current mean/maximum differences against JPL Horizons DE441 are about `0.30s / 0.75s`.
|
||||
- Moon rise/set with other conventions: mean/maximum differences are about `38.77s / 76.22s` against MET Norway's fixed `-0.8333°` convention (Skyfield 1.53 + DE440s). Against IMCCE Miriade, whose horizon convention is not exposed, the mean is about `2m13.46s`; the low-elevation `61°N` sample reaches about `6m41.82s`.
|
||||
- Earth perihelion and aphelion: maximum time difference about `1m28.84s`, maximum distance difference about `0.000000039837 AU`
|
||||
- main-chain lunar position: the current algorithm is a truncated ELP2000/82-style analytical series; across four JPL/Horizons `JDTT` samples in year `-2000`, the maximum difference from JPL/Horizons is about `219.6"` in longitude, `25.8"` in latitude, and `34.3 km` in distance
|
||||
- Moon perigee and apogee: maximum time difference about `15m53.45s`, maximum distance difference about `39.758 km`
|
||||
- maximum lunar declination: maximum time difference about `2.43s`, maximum declination difference about `0.00006431°`
|
||||
- Before comparing rise/set differences, align the refraction model, apparent-radius convention, observer height and local day attribution; the horizon definition often matters more than the ephemeris difference.
|
||||
- Compare solar terms, syzygies and eclipse contacts directly as TT event instants; before turning positional arcseconds into a time error, divide by the local angular rate at that point.
|
||||
- Civil dates also depend on the time zone and time-scale policy, and events near midnight are especially sensitive.
|
||||
- Custom SPK geometric states use ICRF, km, km/day and TDB; high-level scalar and apparent-place entry points accept TT and currently approximate TDB with TT. Call `Instance.State` directly when strict TDB is required; switching to a more accurate kernel does not automatically improve the upper-level coordinate frame, atmosphere or terrain models.
|
||||
- IAU 2006/2000A, polar motion, CPO and the lunar limb profile all require explicit calls or explicit settings and are not enabled automatically in the default chain; see [Coordinate tools](coord.md), [Formula helpers](formula.md) and [Eclipses and transits](eclipse.md#lunar-limb-correction).
|
||||
|
||||
+13
-13
@@ -343,9 +343,9 @@ The explicit-calendar entry points work only for years that have data, and `Anci
|
||||
|
||||
The pre-Qin range uses reconstructed Chunqiu and ancient-six-calendar systems, `-220..-104` uses the Qin/Han Zhuanxu calendar, `-103..1912` uses calendar tables, and `1913` onward uses the modern algorithm.
|
||||
- **Explicit ancient calendars**: use APIs such as `SolarToLunarWithCalendar` / `LunarToSolarWithCalendar` when a specific ancient calendar system is required.
|
||||
- **Data sources**: ancient-calendar support mainly references 《寿星天文历》; [Professor ytliu0's ChineseCalendar data](https://ytliu0.github.io/ChineseCalendar/index_simp.html) is used for validation.
|
||||
- **Data sources**: ancient-calendar support mainly references 《寿星天文历》; [Professor ytliu0's ChineseCalendar data](https://ytliu0.github.io/ChineseCalendar/index_simp.html) is used for cross-validation.
|
||||
|
||||
The modern range follows GB/T 33661-2017 and uses VSOP87/ELP computations for solar terms and new moons.
|
||||
The modern range follows GB/T 33661-2017 and uses the VSOP87 and ELP series for new moons and solar terms.
|
||||
- **Solar terms**: `JieQi` returns modern astronomical solar-term instants; `CalendricalJieQi` returns calendar-compatible solar-term dates.
|
||||
|
||||
## Usage notes
|
||||
@@ -368,7 +368,7 @@ All computation goes through Julian Days. The Gregorian side follows these rules
|
||||
### 4. Time zone
|
||||
The package targets the Chinese calendar, so solar terms and new moons are computed in Beijing time (UTC+8) by default. Applying Chinese lunisolar rules in another time zone can shift dates.
|
||||
|
||||
For exploration and research, the lower-level `Solar` and `Lunar` methods convert between the Gregorian and lunisolar calendars under a **custom time zone**, following the **current Chinese calendar algorithm (GB/T 33661-2017)**.
|
||||
`Solar` and `Lunar` are the low-level entry points; they convert between the Gregorian and lunisolar calendars under a **custom time zone**, following the **current Chinese calendar algorithm (GB/T 33661-2017)**.
|
||||
For standard conversions in Beijing time, use the wrapped `SolarToLunar` and `LunarToSolar` methods.
|
||||
|
||||
**Example**: the calendar rules require the winter solstice to fall in the eleventh lunisolar month. For the 1984 winter solstice:
|
||||
@@ -395,7 +395,7 @@ fmt.Println(calendar.Solar(1985, 1, 1, false, 7.0))
|
||||
|
||||
⚠️ Go's standard-library `time.Time` differs from this package in calendar handling:
|
||||
|
||||
- Before 1582-10-15 Go uses the proleptic Gregorian calendar rather than the Julian calendar. Without `Add`, it generally works as expected.
|
||||
- Before 1582-10-15 Go uses the proleptic Gregorian calendar rather than the Julian calendar; as long as you do not call `time.Time`'s `Add`/`AddDate`, every other use is unaffected.
|
||||
- So **before 1582-10-15, `time.Time.Weekday()` does not agree with this package**:
|
||||
for 1582-10-04 this package reports Thursday, Go reports Monday.
|
||||
|
||||
@@ -412,7 +412,7 @@ For example, 700 is a leap year in the Julian calendar but not in Go's proleptic
|
||||
|
||||
### 6. Julian-only leap days (for example 700-02-29)
|
||||
Before 1582, years divisible by 100 but not 400 (such as 100, 700 or 1500) have a 29 February in the Julian calendar,
|
||||
while Go's `time.Time` uses the proleptic Gregorian calendar and has no such day (`time.Date(700, 2, 29, ...)` normalises to 700-03-01). The library accepts 700-02-29 as an existing day, with these constraints:
|
||||
while Go's `time.Time` uses the proleptic Gregorian calendar and has no such day (`time.Date(700, 2, 29, ...)` normalizes to 700-03-01). The library accepts 700-02-29 as an existing day, with these constraints:
|
||||
|
||||
- `Time.Solar()` / `LunarTime.SolarDate`: the library's canonical output, always the **following day**
|
||||
(700-03-01 for 700-02-29), matching `basic.JD2DateByZone`;
|
||||
@@ -591,8 +591,8 @@ Output:
|
||||
}
|
||||
] // structured lunisolar records, one object per matching historical result
|
||||
1083-11-24 00:00:00 +0800 CST // Gregorian date corresponding to 元丰六年十月十二日
|
||||
[宋神宗 元丰六年十月十二 辽道宗 大康九年十月十二] // the same day also matches a Liao calendar result
|
||||
2026-02-17 00:00:00 +0800 CST // Chinese New Year in 2026
|
||||
[宋神宗 元丰六年十月十二 辽道宗 大康九年十月十二] // the same day also matches the Liao calendar (大康九年十月十二)
|
||||
2026-02-17 00:00:00 +0800 CST // modern lunisolar conversion; the 2026 Chinese New Year falls on 2026-02-17
|
||||
```
|
||||
|
||||
## Solar terms
|
||||
@@ -627,10 +627,10 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
2020-02-04 17:03:20.471614301 +0800 CST // Beginning of Spring
|
||||
2020-12-21 18:02:20.648710727 +0800 CST // Winter Solstice
|
||||
2020-03-20 11:49:37.149532735 +0800 CST // March Equinox
|
||||
2020-03-20 11:49:37.149532735 +0800 CST // same result from direct longitude input
|
||||
2020-02-04 17:03:19.850052595 +0800 CST // Beginning of Spring
|
||||
2020-12-21 18:02:20.574118494 +0800 CST // Winter Solstice
|
||||
2020-03-20 11:49:37.111029624 +0800 CST // March Equinox
|
||||
2020-03-20 11:49:37.111029624 +0800 CST // same result from direct longitude input
|
||||
```
|
||||
|
||||
Calendrical solar-term example:
|
||||
@@ -664,13 +664,13 @@ Output:
|
||||
|
||||
- Every public instant is treated as a civil value, meaning the fields of the `time.Time` are read directly as a UTC label.
|
||||
|
||||
When the library converts civil time to TT, values before 1972-01-01 are treated as UT1 and the exact window uses the built-in leap-second table; for explicit conversion use the root package's `astro.UT1FromUTC` / `astro.TTFromUTC` / `astro.DUT1`.
|
||||
When the library converts civil time to TT, values before 1972-01-01 are treated as UT1 and from then on the built-in leap-second table is used; for explicit conversion use the root package's `astro.UT1FromUTC` / `astro.TTFromUTC` / `astro.DUT1`.
|
||||
- `JieQi` and `WuHou` return civil instants in Beijing time with full fractional-second precision; `CalendricalJieQi*` always returns 00:00 Beijing time on the matching day.
|
||||
- `JD`, `Date2JD`, and `NowJD` are all Julian-day floats on that same civil convention: the Beijing civil day `2026-02-17` corresponds to `2461088.5`.
|
||||
|
||||
### Zero Values and Out-of-Range Input
|
||||
|
||||
- The ten Gregorian days from 1582-10-05 through 1582-10-14 do not exist; the affected entry points report an error instead of silently normalising to a neighbouring date.
|
||||
- The ten Gregorian days from 1582-10-05 through 1582-10-14 do not exist; the affected entry points report an error instead of silently normalizing to a neighbouring date.
|
||||
- `SolarToLunar` / `SolarToLunarByYMD` return an error outside `[-721,3000]`. `LunarToSolar*` is limited to the same Gregorian range, and the boundary lunisolar year `-722` is accepted while its result still falls inside that range.
|
||||
- `CalendricalJieQiWithCalendar` returns an error for the Chunqiu calendar and for years without calendrical solar-term data; `LunarToSolarWithCalendar` accepts an era-name description only when the ancient era table matches and errors otherwise.
|
||||
- `SolarCandidates()` returns a one-element slice holding `Solar()` when there is no second candidate, never an empty slice; `Lunar()` returns a zero-value `LunarTime` when there is no candidate, and its `JulianOnly()` then reports `false`.
|
||||
|
||||
@@ -4,11 +4,7 @@
|
||||
|
||||
`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).
|
||||
Related conventions: observing-angle semantics in [Observing-angle semantics](sun-moon.md#observing-angle-semantics); topocentric geometry in [Fixed-site charts](occultation.md#fixed-site-charts) and [Sun and Moon positions](sun-moon.md#sun-and-moon-position); time scales and observer height in [Time scale conventions](timescale.md) and [Observer height conventions](coord.md#observer-height); overall accuracy in [Applicability and accuracy](accuracy.md).
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -158,6 +154,7 @@ fmt.Printf("HA=%.6f deg\n", coord.HourAngle(date, eq.RA, 115))
|
||||
| `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 |
|
||||
| `Nutation2000A` / `MeanObliquity2006` | IAU 2000A nutation, IAU 2006 mean obliquity | degrees; opt-in only, the default chain is unchanged; the full IAU 2006/2000A rotation is `basic.BiasPrecessionNutation2006` |
|
||||
|
||||
```go
|
||||
// Precess J2000 equatorial coordinates to date and list both nutation series for comparison.
|
||||
@@ -404,11 +401,11 @@ The same observing helpers are also exposed in `sun`, `moon`, `star`, and the se
|
||||
### 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.
|
||||
- Sidereal time: the IAU 2006 ERA route, with apparent sidereal time adding the IAU 2000B longitude nutation; apparent sidereal time is cached per Julian day, and calling a ΔT override invalidates the cache immediately so results follow.
|
||||
- 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.
|
||||
- Topocentric ecliptic coordinates come from the topocentric equatorial position: `TopocentricEcliptic.Lat` is guaranteed to land in `[−90,90]`.
|
||||
- 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.
|
||||
|
||||
+154
-47
@@ -1,9 +1,11 @@
|
||||
# Solar and Lunar Eclipses
|
||||
# Eclipses and Transits
|
||||
|
||||
[中文](../eclipse.md) | [Back to README](../../../README.en.md)
|
||||
|
||||
> Full examples in this manual run from the repository root and write their figures to `doc/img/`.
|
||||
|
||||
This manual covers solar- and lunar-eclipse queries and charts, plus the two Mercury and Venus transit SVG charts. Transit contact times and topocentric visibility come from the `planets` transit entry points, see [Planets: Mercury and Venus transits](planets.md#mercury-and-venus-transits).
|
||||
|
||||
## Contents
|
||||
|
||||
- [Simple example: the 2009 Great Yangtze Eclipse at a site in Shanghai](#simple-example-the-2009-great-yangtze-eclipse-at-a-site-in-shanghai)
|
||||
@@ -23,10 +25,14 @@
|
||||
- [Checks against NASA data](#checks-against-nasa-data)
|
||||
- [Lunar-eclipse SVG](#lunar-eclipse-svg)
|
||||
- [References](#references)
|
||||
- [Lunar limb correction](#lunar-limb-correction)
|
||||
- [Solar and Lunar Eclipse Charts](#solar-and-lunar-eclipse-charts)
|
||||
- [Global visibility maps](#global-visibility-maps)
|
||||
- [Lunar eclipse charts](#lunar-eclipse-charts)
|
||||
- [Time scale and UT1](#time-scale-and-ut1)
|
||||
- [Transit charts](#transit-charts)
|
||||
- [Mercury: 2032-11-13 (Beijing)](#mercury-2032-11-13-beijing)
|
||||
- [Venus: 2012-06-06 (Beijing)](#venus-2012-06-06-beijing)
|
||||
|
||||
## Simple example: the 2009 Great Yangtze Eclipse at a site in Shanghai
|
||||
|
||||
@@ -63,7 +69,7 @@ SVG snippets use the import aliases `eclipsesvg "b612.me/astro/eclipse/svg"` and
|
||||
| --- | --- | --- |
|
||||
| `LocalSolarEclipseOnDate` / `LocalSolarEclipseOnDateNASABulletinSplitK` | Fixed-site solar eclipse query | Returns `(info, bool)`; NASA Split-K by default |
|
||||
| `LunarEclipseOnDate` / `LunarEclipseOnDateDanjon` / `LunarEclipseOnDateChauvenet` | Lunar eclipse query | Same; the suffix forces a shadow-radius model |
|
||||
| `SearchLocalCentralSolarEclipse` / `SolarEclipseCandidates` | Cross-year central-eclipse search / candidate list | The former carries `status.Exhausted` |
|
||||
| `SearchLocalCentralSolarEclipse` / `SolarEclipseCandidates` | Cross-year central-eclipse search / candidate list | The former returns `(info, SolarEclipseLocalSearchStatus)` |
|
||||
| `SolarEclipseCentralPath` / `SolarEclipsePartialFootprints` | Central path and partial-footprint geometry | Options are `SolarEclipsePathOptions` / `SolarEclipsePartialFootprintOptions` |
|
||||
| `SolarEclipseBesselianElements` / `SolarEclipseBesselianMuForPublishedTable` | Besselian elements and the published-table conversion | For table comparison |
|
||||
| `eclipsesvg.SolarEclipseMapSVG` / `LunarEclipseMapSVG` | Global visibility maps | Return `(string, bool)` |
|
||||
@@ -89,7 +95,7 @@ true total
|
||||
```
|
||||
|
||||
- `LocalSolarEclipseOnDate` returns the type, all contact instants, magnitude, obscuration and the solar altitude at greatest eclipse in one call; when you only care whether something happens, read the second return value.
|
||||
- To find "the next central eclipse" across years use `SearchLocalCentralSolarEclipse` (its `status.Exhausted` separates "nothing in the span" from "found"); for a bare candidate list use `SolarEclipseCandidates`.
|
||||
- To find "the next central eclipse" across years use `SearchLocalCentralSolarEclipse` (the `Exhausted` field of the returned `SolarEclipseLocalSearchStatus` separates "nothing in the span" from "found"); for a bare candidate list use `SolarEclipseCandidates`.
|
||||
- On the lunar side the counterparts are `LunarEclipseOnDate` plus the `Danjon` / `Chauvenet` suffixed entry points that force a shadow-radius model.
|
||||
|
||||
### Global visibility maps and lunar charts
|
||||
@@ -154,9 +160,13 @@ There are two solar-radius conventions: the **standard** one (`959.639″` at 1
|
||||
|
||||
For the 2024-04-08 total eclipse the measured convention narrows the path by about 0.6 km per side and shortens totality by about 1.5 s; for the 2023-10-14 annular eclipse it widens the path by about 1.3 km and lengthens the annular phase by about 2.1 s; partial magnitudes change by about `1e-4`.
|
||||
|
||||
This lets you gauge how sensitive eclipse limits and central durations are to the solar radius.
|
||||
Either convention can be selected:
|
||||
|
||||
Either convention can be selected through `eclipse.SolarEclipseOptions{SunRadiusModel: ...}` with `SolarEclipseOnDateWithOptions`, `LocalSolarEclipseOnDateWithOptions`, and the search/panel variants `LastSolarEclipseWithOptions` / `NextSolarEclipseWithOptions` / `ClosestSolarEclipseWithOptions` / `SolarEclipseGeocentricPanelWithOptions`, or through `basic.SolarEclipseWithOptions` / `basic.LocalSolarEclipseWithOptions` and the `SunRadiusModel` field of the various `...Options` structs; the returned `SunRadiusModel` records the convention used, and `basic.SolarEclipseSunSemidiameter` together with the "S.D." rows of the panels use that same convention.
|
||||
- Evaluation: `eclipse.SolarEclipseOptions{SunRadiusModel: ...}` with `SolarEclipseOnDateWithOptions` and `LocalSolarEclipseOnDateWithOptions`
|
||||
- Search and panels: `LastSolarEclipseWithOptions`, `NextSolarEclipseWithOptions`, `ClosestSolarEclipseWithOptions`, `SolarEclipseGeocentricPanelWithOptions`
|
||||
- The `basic` layer: `SolarEclipseWithOptions`, `LocalSolarEclipseWithOptions`, and the `SunRadiusModel` field of the various `...Options` structs
|
||||
|
||||
The returned `SunRadiusModel` records the convention used, and `basic.SolarEclipseSunSemidiameter` together with the "S.D." rows of the panels use that same convention.
|
||||
|
||||
Common entry points:
|
||||
|
||||
@@ -173,7 +183,7 @@ Common entry points:
|
||||
|
||||
`SolarEclipseBesselianElements` returns the same shape of table that published element tables carry: `T0JDE` is the reference instant, `t = (jde - T0JDE) * 24` is the number of TT hours from it, and `X`/`Y`/`D`/`L1`/`L2`/`Mu` are cubics in `t` (`D` and `Mu` in degrees, the rest in equatorial Earth radii), with `TanF1`/`TanF2` constant over the eclipse.
|
||||
|
||||
`L1`/`L2` use the Explanatory Supplement form with its `1/cos f` factor, and a negative umbral `L2` means the Moon's centre has not yet passed the umbra's apex.
|
||||
`L1`/`L2` use the Explanatory Supplement form with its `1/cos f` factor, and a negative umbral `L2` means the Moon's center has not yet passed the umbra's apex.
|
||||
|
||||
By default `T0` is the whole TT hour below greatest eclipse, the window half-width is 3 hours, and five evenly spaced samples inside it are fitted by least squares, matching the fitting convention of published tables.
|
||||
|
||||
@@ -181,7 +191,7 @@ The `Model`, `SunRadiusModel`, `PenumbralK`, `UmbralK` and `DeltaTSeconds` field
|
||||
|
||||
**`Mu` uses a different time argument from published tables and must be shifted before comparison.** Published tables evaluate sidereal time at `T0` itself, while this library uses `UT = TT - ΔT`; the two differ by `DeltaT * 15.041067/3600` degrees. `Mu` stays continuous across the window and is not folded into `[0,360)`, and `SolarEclipseBesselianMuForPublishedTable` shifts only the constant term.
|
||||
|
||||
The library reports the true Greenwich hour angle so that `Mu` stays consistent with its own greatest-eclipse longitude, centre line and contact times; pairing a published table's `Mu` with a correct sidereal time yields a longitude error of about 0.3 degrees (roughly 30 km at the greatest-eclipse latitude of 2024-04-08).
|
||||
The library reports the true Greenwich hour angle so that `Mu` stays consistent with its own greatest-eclipse longitude, center line and contact times; pairing a published table's `Mu` with a correct sidereal time yields a longitude error of about 0.3 degrees (roughly 30 km at the greatest-eclipse latitude of 2024-04-08).
|
||||
|
||||
```go
|
||||
// A Besselian element table; an explicit T0 and DeltaT make it directly comparable.
|
||||
@@ -277,6 +287,7 @@ That `4.8-5.9 s` gap enters every UT-level comparison. Convert it to a ground qu
|
||||
gamma differences median `3.2e-5`, magnitude differences median `4.2e-5`, path-width differences median `0.5 km` (max `8.3 km`) and central-duration differences median `0.25 s` (max `0.59 s`).
|
||||
|
||||
These are the figures that describe geometry and ephemeris accuracy.
|
||||
- **Difference between the two built-in models**: the comparisons above were made while Legacy was the default. On the four regression samples the greatest-eclipse TT difference between the semianalytic model and Legacy is only `0.008-0.198 s`, below the TD-layer residual and far below the `4.8-5.9 s` ΔT convention offset of the UT layer; an `astro_legacy` build passes the same NASA baselines. The tables are therefore not split by model.
|
||||
- **To reproduce a publisher's UT values**: inject their adopted ΔT with `astro.SetDeltaT` (for example `74 s` for 2024) and read UT, which removes the convention offset from the comparison; the injection only affects the conversion and never the TT geometry.
|
||||
- Global eclipse references often publish seconds, so second-level checks are meaningful there.
|
||||
|
||||
@@ -333,14 +344,14 @@ Output:
|
||||
```text
|
||||
true total // Yangshan site has a local total solar eclipse
|
||||
true {136 37 71 true} // Solar Saros 136, member 37/71, verified
|
||||
2009-07-22 08:23:55.092397034 +0800 CST // first contact
|
||||
2009-07-22 09:37:23.088684976 +0800 CST // totality begins
|
||||
2009-07-22 09:40:20.87983489 +0800 CST // greatest eclipse
|
||||
2009-07-22 09:43:19.723805487 +0800 CST // totality ends
|
||||
2009-07-22 11:03:13.914820253 +0800 CST // last contact
|
||||
5m56.635120511s // totality duration
|
||||
magnitude=1.076997 obscuration=1.000000 altitude=57.293 // magnitude, obscuration, solar altitude at greatest eclipse
|
||||
greatest lon=144.1167 lat=24.2193 width=258.3km center=289 // global greatest point, path width, center-line sample count
|
||||
2009-07-22 08:23:55.491912066 +0800 CST // first contact
|
||||
2009-07-22 09:37:23.525415658 +0800 CST // totality begins
|
||||
2009-07-22 09:40:21.314272284 +0800 CST // greatest eclipse
|
||||
2009-07-22 09:43:20.161139667 +0800 CST // totality ends
|
||||
2009-07-22 11:03:14.368167221 +0800 CST // last contact
|
||||
5m56.635724009s // totality duration
|
||||
magnitude=1.076997 obscuration=1.000000 altitude=57.295 // magnitude, obscuration, solar altitude at greatest eclipse
|
||||
greatest lon=144.1154 lat=24.2190 width=258.3km center=289 // global greatest point, path width, center-line sample count
|
||||
```
|
||||
|
||||
### 2012 Xiamen annular eclipse
|
||||
@@ -379,12 +390,12 @@ Output:
|
||||
```text
|
||||
true annular // Xiamen site has a local annular solar eclipse
|
||||
true {128 58 73 true} // Solar Saros 128, member 58/73, verified
|
||||
2012-05-21 05:08:12.878718674 +0800 CST // first contact
|
||||
2012-05-21 06:08:15.561088621 +0800 CST // annularity begins
|
||||
2012-05-21 06:10:25.180663168 +0800 CST // greatest eclipse
|
||||
2012-05-21 06:12:34.80941087 +0800 CST // annularity ends
|
||||
2012-05-21 07:20:54.806806147 +0800 CST // last contact
|
||||
4m19.248322249s // annularity duration
|
||||
2012-05-21 05:08:12.807224392 +0800 CST // first contact
|
||||
2012-05-21 06:08:15.319126546 +0800 CST // annularity begins
|
||||
2012-05-21 06:10:25.112628936 +0800 CST // greatest eclipse
|
||||
2012-05-21 06:12:34.91297096 +0800 CST // annularity ends
|
||||
2012-05-21 07:20:54.736518859 +0800 CST // last contact
|
||||
4m19.593844414s // annularity duration
|
||||
magnitude=0.933289 obscuration=0.872354 altitude=9.565 // magnitude, obscuration, solar altitude at greatest eclipse
|
||||
```
|
||||
|
||||
@@ -476,12 +487,12 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
true 13587 // Yangshan total-eclipse SVG generated, 13587 bytes
|
||||
true 13516 // Xiamen annular-eclipse SVG generated, 13516 bytes
|
||||
true 13603 // Yangshan total-eclipse SVG generated, 13603 bytes
|
||||
true 13532 // Xiamen annular-eclipse SVG generated, 13532 bytes
|
||||
true total // Beijing site has a local total solar eclipse
|
||||
true {145 23 77 true} // Solar Saros 145, member 23/77, verified
|
||||
1m33.329527974s // totality duration near downtown Beijing
|
||||
true 13548 // Beijing total-eclipse SVG generated, 13548 bytes
|
||||
1m33.053206801s // totality duration near downtown Beijing
|
||||
true 13564 // Beijing total-eclipse SVG generated, 13564 bytes
|
||||
```
|
||||
|
||||
Rendered examples:
|
||||
@@ -596,18 +607,18 @@ Output:
|
||||
```text
|
||||
total // eclipse type
|
||||
true {125 49 72 true} // Lunar Saros 125, member 49/72, verified
|
||||
2028-12-31 16:52:05.603753328 +0000 UTC // greatest eclipse
|
||||
2.2739938633996872 1.2461094682708755 // penumbral and umbral magnitudes
|
||||
2028-12-31 14:03:54.239418804 +0000 UTC // P1
|
||||
2028-12-31 15:07:42.171904742 +0000 UTC // U1
|
||||
2028-12-31 16:16:27.306801974 +0000 UTC // U2
|
||||
2028-12-31 17:27:46.228030622 +0000 UTC // U3
|
||||
2028-12-31 18:36:32.270547151 +0000 UTC // U4
|
||||
2028-12-31 19:40:11.575520038 +0000 UTC // P4
|
||||
2.299608256177245 1.2511661731458574 // Chauvenet penumbral and umbral magnitudes
|
||||
2028-12-31 16:52:05.797516107 +0000 UTC // greatest eclipse
|
||||
2.2740102011228953 1.246124858428628 // penumbral and umbral magnitudes
|
||||
2028-12-31 14:03:54.428796172 +0000 UTC // P1
|
||||
2028-12-31 15:07:42.35383898 +0000 UTC // U1
|
||||
2028-12-31 16:16:27.44777888 +0000 UTC // U2
|
||||
2028-12-31 17:27:46.474579274 +0000 UTC // U3
|
||||
2028-12-31 18:36:32.476299405 +0000 UTC // U4
|
||||
2028-12-31 19:40:11.773990094 +0000 UTC // P4
|
||||
2.29962460346294 1.2511815539147877 // Chauvenet penumbral and umbral magnitudes
|
||||
true // local date overlaps an eclipse
|
||||
total // local eclipse type
|
||||
2029-01-01 00:52:05.603753328 +0800 CST // greatest eclipse in UTC+8
|
||||
2029-01-01 00:52:05.797516107 +0800 CST // greatest eclipse in UTC+8
|
||||
```
|
||||
|
||||
### Checks against NASA data
|
||||
@@ -618,6 +629,8 @@ The reference values come from NASA GSFC's lunar-eclipse catalog (greatest eclip
|
||||
|
||||
That is a `4.8-5.9 s` offset, so any UT-level contact comparison carries it wholesale; it is not a lunar-geometry error. The TD level (no ΔT) is the layer that tests the ephemeris and the shadow geometry on its own.
|
||||
|
||||
The table below was measured while Legacy was the default. Re-measuring `2026-03-03` with the semianalytic model gives a greatest-eclipse TD of `+0.083 s` against the `+0.114 s` for Legacy in the table, a `0.031 s` model difference; that is far below the `0.1-1.0 s` convention residuals in the table, so the table is not split by model.
|
||||
|
||||
| Sample | Model | Penumbral magnitude error | Umbral magnitude error | Greatest-eclipse TD difference (no ΔT) | Greatest-eclipse UT difference (with the ΔT convention) |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 2026-03-03 total lunar eclipse | Danjon | -0.000067208 | -0.000069993 | +0.114 s | +5.99 s |
|
||||
@@ -696,7 +709,7 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
true 19816 // lunar-eclipse SVG generated, 19816 bytes
|
||||
true 19832 // lunar-eclipse SVG generated, 19832 bytes
|
||||
```
|
||||
|
||||
Rendered example:
|
||||
@@ -711,6 +724,60 @@ Rendered example:
|
||||
- NASA 2024-03-25 penumbral lunar eclipse diagram: <https://eclipse.gsfc.nasa.gov/LEplot/LEplot2001/LE2024Mar25N.pdf>
|
||||
- NASA lunar eclipse algorithm and history notes: <https://eclipse.gsfc.nasa.gov/LEhistory/LEhistory.html>
|
||||
|
||||
## Lunar limb correction
|
||||
|
||||
The Moon is a circular disk by default; local solar eclipses, global solar eclipses, lunar eclipses and fixed-site occultations can all use the lunar limb profile from `moon/limb`. The profile gives the actual limb radius toward the target and is used to refine contact times.
|
||||
|
||||
| Quantity | Corrected with the limb? |
|
||||
| --- | --- |
|
||||
| Local solar eclipse first and last contacts, central begin/end and central duration | Yes |
|
||||
| Local magnitude and obscuration at greatest eclipse | Yes; the instant of greatest eclipse is unchanged |
|
||||
| Global solar eclipse partial begin/end (P1/P4) and the central duration at greatest eclipse | Yes |
|
||||
| Solar central path northern/southern limits, per-point widths, width at greatest eclipse, central band footprint and longest central duration | Yes, via `SolarEclipsePathOptions.LunarLimb` or the module |
|
||||
| Outer partial visibility boundary (zero-magnitude envelope and the start/end sunrise-sunset curves) | Yes, via `SolarEclipsePartialFootprintOptions.LunarLimb` or the module |
|
||||
| The six lunar-eclipse contacts | Yes, only through the module or context |
|
||||
| Occultation disappearance, reappearance and planetary inner/outer contacts | Yes, see the [occultation manual](occultation.md#lunar-limb-correction) |
|
||||
| Local and global instant of greatest eclipse, global magnitude, type, the center line itself, magnitude isolines, greatest-eclipse isochrones and the greatest-eclipse sunrise-sunset curves | No, still circular |
|
||||
|
||||
Correction rules:
|
||||
|
||||
- Annular and total-lunar inner contacts use the limb on the far side from the contact center.
|
||||
- P1/P4 scan observers along the Earth's limb on both sides of the circular tangent point and keep the earliest (latest) contact.
|
||||
- Central-path limits take the worst point within ±45° of position angle around the central direction: a total limit is where the deepest limb valley still covers the Sun, an annular limit where the highest limb point still lies inside the Sun. Endpoints on the horizon stay circular.
|
||||
- The outer partial boundary is where the highest limb point just touches the near edge of the Sun. Horizon endpoints and the start/end sunrise-sunset curves move along the terminator at the same instant, so the two still meet at one point. Points with solar altitude between 0.05° and 0.5° stay circular. `PartialBoundaryLimbApplied` reports that at least one point moved. Across four eclipses including 2024-04-08 and high-latitude partial ones, the zero-magnitude envelope shifts by a median of 1–4 km and a maximum of about 7 km; the start/end sunrise-sunset curves slide along the terminator and can move more than 10 km at individual points; with the default step, footprint computation takes about 10%–20% longer.
|
||||
|
||||
```go
|
||||
import "b612.me/astro/moon/limb"
|
||||
|
||||
// Per query
|
||||
info, ok := eclipse.LocalSolarEclipseOnDateWithOptions(date, lon, lat, 0,
|
||||
eclipse.SolarEclipseOptions{LunarLimb: limb.Default()})
|
||||
|
||||
// For every local solar eclipse in a context
|
||||
base, err := astro.NewContext(astro.ModelSemianalytic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx := base.WithLunarLimb(limb.Default())
|
||||
info, ok = ctx.Eclipse().LocalSolarEclipseOnDate(date, lon, lat, 0)
|
||||
```
|
||||
|
||||
Charts support it too: every eclipse-chart options struct in `eclipse/svg` carries a `LunarLimb` field. The local solar chart corrects contact times and the magnitude and obscuration at greatest eclipse; both lunar diagrams correct contact times, with the disks still drawn as circles and the instant of greatest eclipse unchanged; the lunar world map moves each phase horizon with the corrected contact; the global solar map corrects the central-band limits, the width at greatest eclipse, the outer partial visibility boundary, the global P1/P4 and the local panel at greatest eclipse (including the central duration), while magnitude isolines, isochrones and the geocentric panel stay circular.
|
||||
|
||||
The layers are independent — a profile with values only at some position angles may correct just some of them — and the global solar map footer lists only the layers actually corrected, e.g. "Lunar limb profile applied to visibility boundary (isolines circular)."; `CentralLimitsLimbApplied` on the central path result records whether the limits and width were corrected.
|
||||
|
||||
Each chart declares the convention after the time-scale note ("Circular limb." or "Limb profile." on local charts), and a custom `FooterNote` does not replace it.
|
||||
|
||||
`LunarLimbApplied` on a result records whether the limb correction actually took effect, and charts use it rather than whether the option is set. Where the profile has no value (`LimbRadiusKM` returns `false`), the radius falls back to the circular limb and that sample does not take part in the correction; when nothing in a result was corrected the field is `false` and the numbers equal the circular result exactly.
|
||||
|
||||
`LunarLimb` in the options takes precedence over the module setting. `eclipse.(*Eclipse).WithLunarLimb` returns a new module and leaves the original unchanged. Any type implementing `eclipse.LunarLimb` can supply another profile; the low-level `basic.LunarEclipseWithLunarLimb` offers the same capability.
|
||||
|
||||
The built-in profile lives in the separate `moon/limb` package. It is a processed product of the ISAS/JAXA SELENE (Kaguya) LALT global topography data (published by DARTS, 16 pixels/degree) and is about 1.5 MB; the data is linked only when that package is imported. The table is indexed by the observer's selenographic longitude and latitude (±9° / ±8°, 1° steps) and position angle (0.1° steps); trilinear interpolation reproduces profiles taken directly from the DEM with an RMS error of about 90 m.
|
||||
|
||||
The corrections are a few seconds: totality at Dallas on 2024-04-08 changes from `227.6 s` to `229.8 s`, Torreón `250.6 s` → `255.0 s`, annularity at Albuquerque on 2023-10-14 `289.4 s` → `290.3 s`, and the contacts of the 2025-09-07 total lunar eclipse move by `-4.4 s` to `+0.2 s`. The total-path limits move by about `2.1 km` / `1.0 km` on average (up to about `3.2 km`), the width at greatest eclipse from `198.6 km` to `196.8 km`, the longest central duration from `268.0 s` to `271.7 s` and the central duration at greatest eclipse from `268.0 s` to `271.2 s`.
|
||||
|
||||
For item-by-item checks, compare with the published values of the *Chinese Astronomical Almanac* (Purple Mountain Observatory): about `-0.8 s` / `+0.2 s` at whole-second precision, and with the Chauvenet convention at 0.1-minute precision a mean error of `1.5 s` circular and `2.2 s` with the limb, both within the published rounding.
|
||||
|
||||
## Solar and Lunar Eclipse Charts
|
||||
|
||||
All five `eclipse/svg` entry points return `(string, bool)`.
|
||||
@@ -719,11 +786,11 @@ A `false` second value means the chart cannot be drawn with the current argument
|
||||
|
||||
| Entry point | Chart | Suggested canvas |
|
||||
| --- | --- | --- |
|
||||
| `LocalSolarEclipseSVG` | Fixed-site solar disk chart (see "Solar-eclipse SVG" above) | 920x720 and up |
|
||||
| `SolarEclipseMapSVG` | Global visibility map, four selectable projections | 1200x800; orthographic globe 1000x1414 |
|
||||
| `LunarEclipseSVG` | Lunar shadow-path diagram | 960x620 and up |
|
||||
| `LunarEclipseMapSVG` | Lunar world visibility map | 1200x800 |
|
||||
| `LunarEclipseDetailedSVG` | Lunar detailed layout (diagram and base map on one page) | 1000x1414 or 1414x1000 |
|
||||
| `LocalSolarEclipseSVG` | Fixed-site solar disk chart (see "Solar-eclipse SVG" above) | 920×720 and up |
|
||||
| `SolarEclipseMapSVG` | Global visibility map, four selectable projections | 1200×800; orthographic globe 1000×1414 |
|
||||
| `LunarEclipseSVG` | Lunar shadow-path diagram | 960×620 and up |
|
||||
| `LunarEclipseMapSVG` | Lunar world visibility map | 1200×800 |
|
||||
| `LunarEclipseDetailedSVG` | Lunar detailed layout (diagram and base map on one page) | 1000×1414 or 1414×1000 |
|
||||
|
||||
A solar global map draws the full partial-visibility region, the total/annular central band, the central line, global phase information and central-line time markers, together with the rise/set lines of first/greatest/last contact, the subsolar point, the shadow-axis entry and exit points, the `P1-P4/U1-U4` contacts, and the sampled penumbral and umbral/antumbral outlines that are off by default and requested on demand.
|
||||
|
||||
@@ -771,7 +838,7 @@ fmt.Println(ok, len(annular), len(total))
|
||||
|
||||
`EclipseMapProjectionOrthographic` gives the NASA-style **orthographic globe**: the view point is the greatest-eclipse point, only the hemisphere facing it is drawn, and the projection boundary is the great circle of the visible hemisphere.
|
||||
|
||||
The orthographic projection uses the NASA-style centered-globe layout; a `1000x1414` canvas is recommended. It does not change the underlying geographic results.
|
||||
The orthographic projection uses the NASA-style centered-globe layout; a `1000×1414` canvas is recommended. It does not change the underlying geographic results.
|
||||
|
||||
```go
|
||||
globe, ok := eclipsesvg.SolarEclipseMapSVG(
|
||||
@@ -786,11 +853,11 @@ globe, ok := eclipsesvg.SolarEclipseMapSVG(
|
||||
|
||||

|
||||
|
||||
The orthographic globe is most comfortable in portrait (e.g. `1000x1414`); landscape (`1200x800`) also renders, with a visibly smaller globe.
|
||||
The orthographic globe is most comfortable in portrait (e.g. `1000×1414`); landscape (`1200×800`) also renders, with a visibly smaller globe.
|
||||
|
||||
#### Polar azimuthal equidistant
|
||||
|
||||
`EclipseMapProjectionNorthPolar` and `EclipseMapProjectionSouthPolar` place the pole at the centre of the canvas, which suits events whose band lies entirely at high latitude.
|
||||
`EclipseMapProjectionNorthPolar` and `EclipseMapProjectionSouthPolar` place the pole at the center of the canvas, which suits events whose band lies entirely at high latitude.
|
||||
|
||||
`EclipseMapProjectionAuto` (the zero value) picks a polar map when that fits, so specify a projection explicitly only when the layout must be fixed; the projection only affects SVG presentation and never the underlying WGS84 geography.
|
||||
|
||||
@@ -885,9 +952,9 @@ Degradable layers record their actual geometry source in `data-source`; the voca
|
||||
|
||||
#### Canvas floors and fallbacks
|
||||
|
||||
- The solar-map floor is **800x560**: a width below 800 or a height below 560 falls back to 960x640. On narrower landscape canvases the map frame overlaps the right-hand data grid horizontally and the panel line spacing drops below 1 px.
|
||||
- The solar-map floor is **800×560**: a width below 800 or a height below 560 falls back to 960×640. On narrower landscape canvases the map frame overlaps the right-hand data grid horizontally and the panel line spacing drops below 1 px.
|
||||
- The partial-region fill is a union of instantaneous footprints, so `PartialStep` values below two minutes are clamped to two minutes; a denser request does not improve the result.
|
||||
- The lunar detailed layout derives its arrangement from `Height`: `640x420` and `800x600` cannot hold the diagram and base-map floors and return `false`, while `1000x1414` and `1414x1000` render normally.
|
||||
- The lunar detailed layout derives its arrangement from `Height`: `640×420` and `800×600` cannot hold the diagram and base-map floors and return `false`, while `1000×1414` and `1414×1000` render normally.
|
||||
|
||||
### Lunar eclipse charts
|
||||
|
||||
@@ -941,7 +1008,7 @@ penumbral, ok := eclipsesvg.LunarEclipseMapSVG(
|
||||
|
||||
#### Detailed layout
|
||||
|
||||
The detailed layout combines both lunar charts on one page: a centred summary (greatest eclipse, penumbral/umbral magnitude, gamma, penumbral/umbral radii, Moon distance, Saros series), geocentric coordinate blocks for Sun and Moon on either side, the shadow-path diagram, three columns for duration, arc-minute scale and contact times, and the world visibility base map with its legend underneath.
|
||||
The detailed layout combines both lunar charts on one page: a centered summary (greatest eclipse, penumbral/umbral magnitude, gamma, penumbral/umbral radii, Moon distance, Saros series), geocentric coordinate blocks for Sun and Moon on either side, the shadow-path diagram, three columns for duration, arc-minute scale and contact times, and the world visibility base map with its legend underneath.
|
||||
|
||||
The shadow geometry comes from `basic.LunarEclipseShadowGeometryAt`, where **gamma uses the Earth equatorial radius while the penumbral/umbral radii are in degrees** - to convert them into Earth radii, multiply by the Earth parallax at the Moon.
|
||||
|
||||
@@ -990,3 +1057,43 @@ ut1, ok := eclipsesvg.SolarEclipseMapSVG(
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Transit charts
|
||||
|
||||
Transit contact times, topocentric visibility and `LocalTransit` live in [Planets: Mercury and Venus transits](planets.md#mercury-and-venus-transits); this section covers only the two `eclipse/svg` charts. `eclipse/svg` has two transit renderers, both returning `(string, bool)`; `date` selects the closest transit, and a same-day query finds that day's transit.
|
||||
|
||||
| Entry | Chart | Suggested canvas |
|
||||
| --- | --- | --- |
|
||||
| `TransitMapSVG` | Geocentric global visibility: entire transit, transit at sunrise, transit at sunset, not visible | 960×640 and up |
|
||||
| `TransitLocalSVG` | Topocentric solar disk: north up, east left, planet-center track, planet disk at each contact and at greatest, contact table | 960×680 and up |
|
||||
|
||||
Regions follow whether the Sun's center is above the apparent horizon at C1, greatest and C4, with standard refraction (a horizon circle of 90°34′). Isochrones are off by default; enable them bitwise with `Isochrones`: `TransitIsochroneC1` … `TransitIsochroneC4`, or the combinations `TransitIsochroneExternal` (C1 and C4) and `TransitIsochroneAll`. C2/C3 are dropped for a grazing transit without internal contacts. Isochrones are topocentric contact times drawn only where the Sun is above the apparent horizon; a non-positive `IsochroneStep` uses 20 s for Mercury and 1 min for Venus. A transit is visible from roughly half the globe, so the map always shows the whole world; `Projection` selects one of the four projections.
|
||||
|
||||
```go
|
||||
venus := time.Date(2012, 6, 6, 0, 0, 0, 0, time.UTC)
|
||||
world, ok := eclipsesvg.TransitMapSVG(eclipsesvg.TransitVenus, venus, eclipsesvg.TransitMapSVGOptions{
|
||||
Isochrones: eclipsesvg.TransitIsochroneExternal,
|
||||
IsochroneStep: 10 * time.Minute,
|
||||
Language: "en",
|
||||
})
|
||||
local, ok := eclipsesvg.TransitLocalSVG(eclipsesvg.TransitVenus, venus, 116.4, 39.9, 50,
|
||||
eclipsesvg.TransitLocalSVGOptions{Language: "en"})
|
||||
```
|
||||
|
||||
On the disk chart, track parts with the Sun's center below the apparent horizon are grey and dashed, and the table adds sunrise or sunset during the transit. `TransitLocalSVG` returns `false` when the site sees no geometric transit. The topocentric contact method is described in [Planets](planets.md#mercury-and-venus-transits).
|
||||
|
||||
### Mercury: 2032-11-13 (Beijing)
|
||||
|
||||
2032-11-13 is the next Mercury transit visible from Beijing. Beijing sees first contact, second contact and greatest, with the Sun only `+0.2°` high at greatest; third and fourth contact happen after sunset (Sun about `-24°`) and are drawn grey and dashed on the disk chart.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### Venus: 2012-06-06 (Beijing)
|
||||
|
||||
The Venus transit of 2012-06-06 was visible in full from Beijing, with `6h39m4s` from first to fourth contact and solar altitudes of `+14°` to `+71°` at the contacts.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
# Explicit ephemeris instances
|
||||
|
||||
[中文](../ephemeris.md) | [Back to README](../../../README.en.md)
|
||||
|
||||
Package-level functions use the current ephemeris model of the process, which is the semianalytic model by default. To use several models side by side in one process, or to plug in an external ephemeris (SPK, a home-grown engine), pin the ephemeris down with `astro.Context`; when geometric state (position and velocity) has to be read directly, drop down to `astro.Instance`.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Switching to another built-in model](#switching-to-another-built-in-model)
|
||||
- [Coordinates, altitude and rise/set with a Context](#coordinates-altitude-and-riseset-with-a-context)
|
||||
- [Querying events by date](#querying-events-by-date)
|
||||
- [Plugging in an external ephemeris](#plugging-in-an-external-ephemeris)
|
||||
- [Stacking multiple sources](#stacking-multiple-sources)
|
||||
- [Engine contract](#engine-contract)
|
||||
- [Limits](#limits)
|
||||
|
||||
## Switching to another built-in model
|
||||
|
||||
`astro.NewContext` creates an independent context, and the model is fixed when the context is created:
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"b612.me/astro"
|
||||
)
|
||||
|
||||
date := time.Date(2026, 2, 17, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
|
||||
semi, err := astro.NewContext(astro.ModelSemianalytic) // the model used by default builds
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
legacy, err := astro.NewContext(astro.ModelLegacy)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ra1, dec1 := semi.Sun().ApparentRaDec(date)
|
||||
ra2, dec2 := legacy.Sun().ApparentRaDec(date)
|
||||
fmt.Printf("%.8f %.8f\n", ra1, dec1)
|
||||
fmt.Printf("%.8f %.8f\n", ra2, dec2)
|
||||
```
|
||||
|
||||
```text
|
||||
330.64554489 -11.99819781
|
||||
330.64554785 -11.99820252
|
||||
```
|
||||
|
||||
Once a context is created its model is fixed, and a later `astro.SetModel` does not affect it. `SetModel` changes the default model of the package-level functions and accepts only the two built-in models; any other model returns `astro.ErrUnknownModel` and leaves the global state unchanged. A custom engine must be passed explicitly to a `Context` or an `Instance`. `NewContext` returns `ErrUnknownModel` for an unknown model and `ErrInvalidEngine` when the engines do not fit the model (an engine passed with a built-in model, or `ModelCustom` without exactly one non-nil engine); `NewModelInstance` accepts only built-in models.
|
||||
|
||||
## Coordinates, altitude and rise/set with a Context
|
||||
|
||||
The methods returned by the accessors take the same arguments and return the same values as the package-level functions of the same name, so `sun.Xxx` becomes `ctx.Sun().Xxx`:
|
||||
|
||||
```go
|
||||
ctx, err := astro.NewContext(astro.ModelSemianalytic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
date := time.Date(2026, 2, 17, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
lon, lat := 116.4, 39.9
|
||||
|
||||
// The Sun: geocentric apparent right ascension/declination, topocentric altitude/azimuth
|
||||
ra, dec := ctx.Sun().ApparentRaDec(date)
|
||||
alt := ctx.Sun().Altitude(date, lon, lat)
|
||||
az := ctx.Sun().Azimuth(date, lon, lat)
|
||||
fmt.Printf("Sun ra=%.6f dec=%.6f alt=%.6f az=%.6f\n", ra, dec, alt, az)
|
||||
|
||||
// The Moon: use GeocentricApparentRaDec for geocentric coordinates, topocentric quantities take the longitude and latitude directly
|
||||
moonRa, moonDec := ctx.Moon().GeocentricApparentRaDec(date)
|
||||
moonAlt := ctx.Moon().Altitude(date, lon, lat)
|
||||
moonRise, err := ctx.Moon().RiseTime(date, lon, lat, 0, true)
|
||||
fmt.Printf("Moon ra=%.6f dec=%.6f alt=%.6f rise=%s err=%v\n",
|
||||
moonRa, moonDec, moonAlt, moonRise.Format("15:04:05"), err)
|
||||
|
||||
// Planets are reached through accessors named after their packages
|
||||
marsRa, marsDec := ctx.Mars().ApparentRaDec(date)
|
||||
marsAlt := ctx.Mars().Altitude(date, lon, lat)
|
||||
fmt.Printf("Mars ra=%.6f dec=%.6f alt=%.6f\n", marsRa, marsDec, marsAlt)
|
||||
```
|
||||
|
||||
```text
|
||||
Sun ra=330.645545 dec=-11.998198 alt=37.682918 az=171.221286
|
||||
Moon ra=327.197499 dec=-14.621205 alt=34.596340 rise=06:59:57 err=<nil>
|
||||
Mars ra=322.193276 dec=-16.023967 alt=34.060823
|
||||
```
|
||||
|
||||
The instant is 2026-02-17 12:00 CST, and the site is 116.4° east and 39.9° north. `RiseTime` returns an error instead of a zero time when it finds no rise or set within the local civil day.
|
||||
|
||||
| Entry point | Package | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `Sun()` | `sun` | Geocentric coordinates, topocentric altitude/azimuth/zenith distance, rise and set and twilight, apparent diameter, physical ephemeris |
|
||||
| `Moon()` | `moon` | Geocentric and topocentric coordinates, altitude/azimuth, rise and set, lunar phase, geocentric distance, libration |
|
||||
| `Mercury()` … `Neptune()` | The planet packages | Apparent coordinates, altitude/azimuth, rise and set, phase and apparent magnitude, events such as opposition, conjunction and greatest elongation |
|
||||
| `Eclipse()` | `eclipse` | Solar and lunar eclipse queries, local contact times, eclipse paths |
|
||||
| `Phenomena()` | `phenomena` | Ecliptic longitude crossings, stations, closest approach, rise and set at any altitude, heliacal visibility |
|
||||
| `GeoJSON()` | `geojson` | Map data for celestial events |
|
||||
|
||||
The `...N` methods use the fast table, for example `ctx.Sun().AltitudeN(date, lon, lat, n)` and `ctx.Moon().TrueLoN(date, n)`; see [Accuracy and performance](accuracy.md) for the meaning of `n`.
|
||||
|
||||
## Querying events by date
|
||||
|
||||
Event queries also hang off the modules:
|
||||
|
||||
```go
|
||||
date := time.Date(2026, 3, 3, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
|
||||
info, ok := ctx.Eclipse().LunarEclipseOnDate(date)
|
||||
next := ctx.Eclipse().NextLunarEclipse(date)
|
||||
fmt.Println(ok, info.Maximum.Format("2006-01-02 15:04 MST"), info.Type)
|
||||
fmt.Println(next.Maximum.Format("2006-01-02 15:04 MST"), next.Type)
|
||||
```
|
||||
|
||||
```text
|
||||
true 2026-03-03 19:33 CST total
|
||||
2026-03-03 19:33 CST total
|
||||
```
|
||||
|
||||
`ok` is `true` only when a lunar eclipse occurs on that date; the example date 2026-03-03 has one. Filtering and searching both use the instance of that context. The Saros tables of the built-in models are selected according to the instance model; a custom engine can describe an arbitrary trajectory, so no Saros number is inferred and `HasSaros` is `false` in the result.
|
||||
|
||||
## Plugging in an external ephemeris
|
||||
|
||||
`spk.Open` reads a JPL SPK kernel and passes it to `NewContext` as an `astro.Engine`; afterwards the calls are exactly the same as with a built-in model:
|
||||
|
||||
```go
|
||||
import "b612.me/astro/planet/spk"
|
||||
|
||||
kernel, err := spk.Open("de440s.bsp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer kernel.Close()
|
||||
|
||||
custom, err := astro.NewContext(astro.ModelCustom, kernel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Reuse the date above (2026-02-17 12:00 CST) and the longitude and latitude
|
||||
ra, dec := custom.Sun().ApparentRaDec(date)
|
||||
alt := custom.Sun().Altitude(date, lon, lat)
|
||||
marsRa, marsDec := custom.Mars().ApparentRaDec(date)
|
||||
marsAlt := custom.Mars().Altitude(date, lon, lat)
|
||||
moonAlt := custom.Moon().Altitude(date, lon, lat)
|
||||
fmt.Printf("Sun ra=%.6f dec=%.6f alt=%.6f\n", ra, dec, alt)
|
||||
fmt.Printf("Mars ra=%.6f dec=%.6f alt=%.6f\n", marsRa, marsDec, marsAlt)
|
||||
fmt.Printf("Moon alt=%.6f\n", moonAlt)
|
||||
```
|
||||
|
||||
```text
|
||||
Sun ra=330.645545 dec=-11.998198 alt=37.682918
|
||||
Mars ra=322.193276 dec=-16.023967 alt=34.060823
|
||||
Moon alt=34.596325
|
||||
```
|
||||
|
||||
The results agree closely with the built-in semianalytic model at the same instant; here the lunar altitude differs by about `0.05″`.
|
||||
|
||||
Use `Instance` instead when the raw geometric state (position and velocity) is needed:
|
||||
|
||||
```go
|
||||
inst := astro.NewInstance(kernel)
|
||||
jdTDB := calendar.Date2JD(astro.TDBFromTT(date))
|
||||
pos, vel, err := inst.State(astro.BodyMoon, astro.BodyEarth, jdTDB)
|
||||
start, end, ok := inst.Coverage(astro.BodyMoon)
|
||||
fmt.Printf("pos=%.1f,%.1f,%.1f km\nvel=%.1f,%.1f,%.1f km/day\nerr=%v coverage=%.1f..%.1f ok=%v\n",
|
||||
pos[0], pos[1], pos[2], vel[0], vel[1], vel[2], err, start, end, ok)
|
||||
```
|
||||
|
||||
```text
|
||||
pos=312305.1,-204135.4,-98208.0 km
|
||||
vel=48600.2,64310.3,36543.8 km/day
|
||||
err=<nil> coverage=2396752.5..2506352.5 ok=true
|
||||
```
|
||||
|
||||
`State` takes TDB epochs and returns the ICRF geometric position (km) and velocity (km/day). `Coverage` gives the usable epoch interval of that body, and going outside it returns `ErrEphemerisOutOfRange`.
|
||||
|
||||
## Stacking multiple sources
|
||||
|
||||
`astro.NewStackInstance` composes several engines in argument order, and the source passed first has priority. Small-body kernels often provide only the state of the target relative to the Sun, and Stack composes a state of the target relative to the solar-system barycenter across sources:
|
||||
|
||||
```go
|
||||
de440, _ := spk.Open("de440s.bsp")
|
||||
ceres, _ := spk.Open("ceres.bsp")
|
||||
defer de440.Close()
|
||||
defer ceres.Close()
|
||||
|
||||
stack := astro.NewStackInstance(de440, ceres)
|
||||
|
||||
// A small-body kernel usually covers a narrow range, so the query epoch must fall inside its coverage
|
||||
smallDate := time.Date(2026, 1, 2, 0, 0, 0, 0, time.UTC)
|
||||
jdTT := calendar.Date2JD(astro.TTFromUTC(smallDate))
|
||||
dir, dist, tau, err := stack.Apparent(astro.Body(20000001), jdTT)
|
||||
fmt.Printf("%.8f,%.8f,%.8f\ndist=%.0f km tau=%.5f day err=%v\n", dir[0], dir[1], dir[2], dist, tau, err)
|
||||
```
|
||||
|
||||
```text
|
||||
0.98097637,0.17021858,-0.09333272
|
||||
dist=417020638 km tau=0.01610 day err=<nil>
|
||||
```
|
||||
|
||||
`astro.Body` is an `int`, so any NAIF identifier can be written directly; Ceres is `20000001`. The Ceres kernel in this example covers only 2026-01-01 to 01-03 (`stack.Coverage` returns `2461041.5..2461043.5`); going outside the coverage returns `ErrEphemerisOutOfRange` and never silently switches to another source.
|
||||
|
||||
## Engine contract
|
||||
|
||||
- `Engine.State(target, center, jdTDB)`: ICRF geometric position (km) and velocity (km/day), with TDB epochs. A custom engine must implement it.
|
||||
- The optional `ApparentEngine.Apparent(target, jdTT)`: geocentric ICRF unit apparent direction, distance (km) and light time (days) at a TT epoch. An engine that implements only `State` (such as SPK) goes through the generic vector apparent-place chain: iterate the light time, then add solar gravitational deflection and relativistic aberration; this requires the engine to provide states of the Earth, the Sun and the target relative to the solar-system barycenter (`BodySSB`).
|
||||
- Engines are called concurrently and must be read-only and reentrant; the library does not start goroutines.
|
||||
- A failure of a custom engine does not fall back to the built-in tables: scalar entry points return `NaN`, the `Maximum` of a lunar eclipse core is `NaN` and each phase flag is `false`, and date queries report a miss.
|
||||
- Scalar coordinates and the `basic` lunar eclipse entry points take TT epochs; the geometric engine currently approximates TDB with TT, so call `Instance.State` directly when strict TDB is required.
|
||||
|
||||
## Limits
|
||||
|
||||
- `EphemerisLegacy` instances have scalar entry points only and no geometric engine: `Instance.State` returns `astro.ErrEphemerisNoEngine`, and `Coverage` and `Apparent` report unavailable. Use the semianalytic model or a custom engine when positions and velocities are needed.
|
||||
- An explicit instance only decides which ephemeris is used, and does not guarantee a smaller binary: both built-in tables are linked into the program.
|
||||
- The usable range of the semianalytic model is TT JD `-1931455.0 … 5373545.0`, and evaluation still extrapolates outside it; the modern planetary segment is JD `2086295.0 … 2816795.0`. See [Accuracy and performance](accuracy.md) for the errors.
|
||||
- `Stack.Coverage` is the union of the source intervals, may contain gaps in between, and does not guarantee that every body needed by a center chain (the path of centers Stack searches for between its sources to compose a state) is available; rely on what `State` returns at a specific epoch.
|
||||
- Most `basic` entry points that support instances carry the `WithInstance` suffix, and the `n`-parameterised ones carry `NWithInstance`; passing `nil` means the current global model of the process. The exceptions are the shadow-radius entry `LunarEclipseWithLunarLimb` and the 18 atmospheric rise/set entries, which take `*Instance` as their last parameter. Direct calls to these entry points are normally unnecessary.
|
||||
@@ -2,15 +2,16 @@
|
||||
|
||||
[中文](../formula.md) | [Back to README](../../../README.en.md)
|
||||
|
||||
`formula` holds the common formulas that are unrelated to a specific date or ephemeris; they suit popular-science estimates, novel settings, and teaching demonstrations.
|
||||
`formula` holds pure formulas that read no ephemeris tables, covering radiation, photometry, telescopes, the atmosphere and Earth orientation for estimates and observing aids.
|
||||
|
||||
It performs no time-scale conversion and reads no ephemeris tables: the inputs are only instantaneous or constant parameters such as temperature, wavelength, distance, aperture, and altitude.
|
||||
It performs no time-scale conversion and reads no global ephemeris. Most inputs are physical quantities such as temperature, wavelength, distance, aperture and altitude; `TIOLocatorArcsec` is the exception that accepts a TT Julian date.
|
||||
|
||||
Altitude and zenith-distance conventions follow [Observing-angle semantics](sun-moon.md#observing-angle-semantics); when coordinate-layer refraction is needed, use `coord`'s [airmass](coord.md#airmass); overall accuracy and applicability are in the manual [Scope And Accuracy](accuracy.md).
|
||||
Altitude and zenith-distance conventions follow [Observing-angle semantics](sun-moon.md#observing-angle-semantics); overall accuracy and applicability are in [Accuracy and performance](accuracy.md).
|
||||
|
||||
## Contents
|
||||
|
||||
- [Magnitude, synodic period and blackbody radiation](#magnitude-synodic-period-and-blackbody-radiation)
|
||||
- [Refraction, extinction and Earth orientation](#refraction-extinction-and-earth-orientation)
|
||||
- [API Reference](#api-reference)
|
||||
- [Blackbody and Radiation](#blackbody-and-radiation)
|
||||
- [Synodic Period](#synodic-period)
|
||||
@@ -31,6 +32,35 @@ Altitude and zenith-distance conventions follow [Observing-angle semantics](sun-
|
||||
- [Zero Values and Invalid Input](#zero-values-and-invalid-input)
|
||||
- [Accuracy and Applicability](#accuracy-and-applicability)
|
||||
|
||||
## Refraction, extinction and Earth orientation
|
||||
|
||||
| Entry point | Input and output convention |
|
||||
| --- | --- |
|
||||
| `ApparentAltitude` / `TrueAltitude` | True/apparent altitude conversion in degrees; pressure hPa and temperature °C |
|
||||
| `RefractionFromTrueAltitude` / `RefractionFromApparentAltitude` | Refraction in degrees, added to true altitude or subtracted from apparent altitude |
|
||||
| `ExtinctionSchaefer` | V-band zenith extinction components and `Total`, in magnitudes per airmass |
|
||||
| `TIOLocatorArcsec` | TT Julian date to the TIO locator s′ in arcseconds |
|
||||
| `PolarMotionMatrix` | `xp`, `yp` and s′ in arcseconds; a matrix acting on column vectors from TIRS to ITRS |
|
||||
| `CelestialPoleOffsetMatrix` | dX/dY in mas; a first-order small-angle correction matrix multiplying an existing celestial rotation on the left |
|
||||
|
||||
Abbreviations in the table: `TIO` is the terrestrial intermediate origin, `TIRS` and `ITRS` are the terrestrial intermediate and terrestrial reference systems, `s′` is the TIO locator, and `CPO` is the celestial pole offset (`dX`, `dY`) in `mas` (milliarcseconds).
|
||||
|
||||
```go
|
||||
apparent := formula.ApparentAltitude(5, 1010, 10)
|
||||
trueAltitude := formula.TrueAltitude(apparent, 1010, 10)
|
||||
extinction := formula.ExtinctionSchaefer(100, 20, 50, 39.9, 120)
|
||||
polarMotion := formula.PolarMotionMatrix(0.1, 0.2, formula.TIOLocatorArcsec(2460000.5))
|
||||
_ = trueAltitude
|
||||
_ = extinction.Total
|
||||
_ = polarMotion
|
||||
```
|
||||
|
||||
The standard refraction constants are 1010 hPa and 10 °C. Pressure must be positive, temperature above absolute zero and inputs finite. Both directions use the same model; a missing inverse returns `NaN`. Refraction is zero at altitudes ≤−5° or ≥90°. These are empirical-model boundaries, not a guarantee for precision near-horizon observing.
|
||||
|
||||
`ExtinctionSchaefer` uses site elevation in metres, unlike the ellipsoidal height of most observing APIs. Humidity is a percentage in `(0,100)`; latitude and solar right ascension are degrees. Assign `Total` to `phenomena.HeliacalOptions.ExtinctionCoefficient` to use it; when it is not assigned, the visibility calculation uses that option's own default extinction. Do not reuse a V-band coefficient for a different band without conversion.
|
||||
|
||||
Callers supply polar-motion and CPO parameters. The package downloads no IERS (International Earth Rotation and Reference Systems Service) data and does not automatically apply these corrections to default rise/set, eclipse or occultation calculations. The arcseconds of s′ and xp/yp differ from the mas of dX/dY; matrix elements are dimensionless.
|
||||
|
||||
## Magnitude, synodic period and blackbody radiation
|
||||
|
||||
```go
|
||||
@@ -299,11 +329,11 @@ Pressure- and temperature-corrected versions live in [Coordinate tools](coord.md
|
||||
- Telescope family: aperture millimeters; `DawesLimitArcsec` and `RayleighLimitArcsec` return **arcseconds**, not degrees; `LightGatheringPowerRatio` and `LimitingMagnitudeEmpirical` return a dimensionless ratio and a magnitude respectively.
|
||||
- Airmass family: altitude (or zenith distance) in degrees; the return value is the dimensionless relative airmass with 1 at the zenith.
|
||||
- Synodic period: the unit is chosen by the caller, both inputs must match, and the output matches them; the package does not assume days.
|
||||
- This package produces no apparent diameter or apparent radius. Apparent-radius fields in the eclipse and occultation manuals are in arcseconds; here only the two Dawes/Rayleigh limits use arcseconds while all other angles are degrees, so do not interchange them.
|
||||
- This package produces no apparent diameter or radius. Ordinary angles and refraction use degrees; Dawes/Rayleigh and the TIO locator use arcseconds, polar-motion inputs arcseconds and CPO inputs mas.
|
||||
|
||||
### Time Scales
|
||||
|
||||
- No entry point accepts an instant or performs any time-scale conversion: the formulas depend only on parameters such as temperature, wavelength, distance, aperture, and altitude, and are independent of UTC, UT1, and TT.
|
||||
- The package performs no time-scale conversion. `TIOLocatorArcsec` accepts a TT Julian date; the other formulas use caller-supplied physical quantities or periods without reading global time-scale settings.
|
||||
- The synodic period is a length of time, not the instant of the next conjunction.
|
||||
|
||||
Landing on a date requires the conjunction APIs in the planet packages plus civil time, whose convention is in the manual [Time Scale Conventions](timescale.md).
|
||||
@@ -311,10 +341,10 @@ Pressure- and temperature-corrected versions live in [Coordinate tools](coord.md
|
||||
|
||||
### Angle Quadrants and Degree/Radian Boundaries
|
||||
|
||||
- All angle parameters are degrees, are converted to radians internally, and come back as degrees or arcseconds; radians never leak to the caller.
|
||||
- Altitude and zenith distance are both limited to `[0,90]`: the horizon is `0°`, the zenith is `+90°`, and zenith distance is the complement of altitude.
|
||||
- Ordinary angles are degrees, polar-motion angles arcseconds and CPO mas. Follow each function's declared units; internal radians never leak to the caller.
|
||||
- Airmass models limit altitude and zenith distance to `[0,90]`, with no quadrant folding. Refraction functions have their own low-altitude boundary; do not apply the airmass range to them.
|
||||
|
||||
The package performs no quadrant folding, so a negative altitude (below the horizon) is simply invalid and is never folded to the zenith or replaced by an absolute value.
|
||||
Negative altitude is invalid for the airmass models rather than folded to the zenith or replaced by an absolute value.
|
||||
- Zenith distance `z` and altitude `h` satisfy `z = 90° − h`; `AirmassPlaneParallel` takes `h` and `AirmassPlaneParallelByZenithDistance` takes `z`, and both must agree for the same geometry.
|
||||
- Dawes/Rayleigh return arcseconds; divide by 3600 to compare with the degree-based angles in the other manuals.
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
## Contents
|
||||
|
||||
- [Exporting solar-eclipse GeoJSON](#exporting-solar-eclipse-geojson)
|
||||
- [Exporting transit GeoJSON](#exporting-transit-geojson)
|
||||
- [API Reference](#api-reference)
|
||||
- [Usage examples](#usage-examples)
|
||||
- [Choosing a projection](#choosing-a-projection)
|
||||
@@ -70,6 +71,25 @@ func main() {
|
||||
|
||||
A partial eclipse may have no central path, so `centralPath` may be `nil`. This program writes GeoJSON to standard output for redirection to a file. SVG and KML examples follow.
|
||||
|
||||
## Exporting transit GeoJSON
|
||||
|
||||
The transit entry points take a geocentric `TransitInfo` and partition the globe exactly like the transit visibility map:
|
||||
|
||||
```go
|
||||
m := mercury.NextTransit(time.Date(2026, 10, 12, 0, 0, 0, 0, cst))
|
||||
data, err := geojson.MarshalMercuryTransit(m, geojson.PlanetTransitOptions{
|
||||
BoundaryPoints: 360,
|
||||
IsochroneStep: 5 * time.Minute,
|
||||
TimeMarkers: &geojson.TimeMarkerOptions{Step: time.Hour, Location: cst},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
```
|
||||
|
||||
For Venus, use `venus.NextTransit` with `MarshalVenusTransit`; the options are the same. `date` only has to fall before the transit: `NextTransit` returns the next one after it, and `ClosestTransit` returns the nearest in either direction. An invalid `TransitInfo` (`Valid` false) makes the export fail.
|
||||
|
||||
## API Reference
|
||||
|
||||
SVG snippets use the import aliases `eclipsesvg "b612.me/astro/eclipse/svg"` and, for occultations, `moonsvg "b612.me/astro/moon/svg"`. Dates and time zones reuse the first example.
|
||||
@@ -82,6 +102,7 @@ SVG snippets use the import aliases `eclipsesvg "b612.me/astro/eclipse/svg"` and
|
||||
| `StarOccultationPathSVG` / `PlanetOccultationPathSVG` | Occultation global path maps | Return `(string, error)` |
|
||||
| `MarshalSolarEclipse` / `MarshalSolarEclipseWithTimeMarkers` | Solar eclipse GeoJSON | Without and with time markers |
|
||||
| `MarshalLunarEclipse` / `MarshalLunarEclipseWithTimeMarkers` / `MarshalLunarEclipseWithOptions` | Lunar eclipse GeoJSON | Same three |
|
||||
| `MarshalMercuryTransit` / `MarshalVenusTransit` | Planetary transit GeoJSON | Options in `PlanetTransitOptions` |
|
||||
| `MarshalStarOccultation` / `MarshalPlanetOccultation` (and `...WithTimeMarkers`) | Occultation GeoJSON | Same pair |
|
||||
| `NewSolarEclipseShadowSolver` / `MarshalSolarEclipseShadowInstant` | Single-instant footprint and its GeoJSON | For time-axis scrubbing |
|
||||
| `TimeMarkerOptions` | Marker `Step`, `Location` and `TimeScale` | `Step` defaults to 30 minutes, at most 1440 markers |
|
||||
@@ -134,7 +155,7 @@ fmt.Println(err, json.Valid(data), len(data))
|
||||
|
||||
- `WithTimeMarkers` appends Point features with `role=time-marker`: `label` is localised through `Location`, while `time` stays UTC RFC 3339.
|
||||
- `Step` defaults to 30 minutes, positive values are at least one minute, and one export carries at most 1440 markers; use the unsuffixed `MarshalSolarEclipse` when no markers are wanted.
|
||||
- Lunar eclipses and occultations have symmetric entry points (`MarshalLunarEclipse*`, `MarshalStarOccultation*`, `MarshalPlanetOccultation*`); GeoJSON carries no basemap, boundary, style or projection, leaving Web Mercator and tile choices to the application.
|
||||
- Lunar eclipses and occultations have symmetric entry points (`MarshalLunarEclipse*`, `MarshalStarOccultation*`, `MarshalPlanetOccultation*`).
|
||||
|
||||
### Declaring the scale and UT1
|
||||
|
||||
@@ -165,7 +186,15 @@ true <nil> true 4217
|
||||
|
||||
- The single-instant API computes only "this instant's umbral/penumbral footprint" and the station geometry; it produces no visibility bands, magnitude contours, rise/set boundaries or limits, and returns an empty value (not an error) when the umbra misses the Earth.
|
||||
- Compare `interp_signature` before interpolating: only identical neighbouring instants are safe to interpolate vertex by vertex; a flipped `closed`, a changed segment or vertex count, or an empty/non-empty transition (near U1/U4) all require exact geometry instead.
|
||||
- Degradable layers record their actual geometry source in `data-source`; the vocabulary is under [Map Projections](#map-projections).
|
||||
- Degradable layers record their actual geometry source in `data-source`; layers without a degraded branch carry no such attribute. The legal values are listed below.
|
||||
|
||||
Grouped by figure family, the `data-source` values are listed below; the `eclipse/svg` package comment gives the full meaning of each:
|
||||
|
||||
- Solar-eclipse boundaries and visibility bands: `partial-band-union`, `sampled-footprint-sweep`, `partial-band-contours`, `besselian-critical-envelope`, `paired-limit-chords`, `sampled-open-sweep`, `central-path-limits`
|
||||
- Solar-eclipse instants and magnitude lines: `rise-set-phase-lines`, `magnitude-contours`, `greatest-time-isochrones`
|
||||
- Instantaneous outlines: `penumbral-outlines`, `central-shadow-outlines`
|
||||
- Lunar eclipses: `p1-p4-visibility-regions`, `p1-p4-horizon-boundaries`
|
||||
- Transits: `c1-c4-visibility-regions`, `c1-c4-horizon-boundaries`, `transit-contact-isochrones`
|
||||
|
||||
## Map Projections
|
||||
|
||||
@@ -177,14 +206,14 @@ The automatic projection (the zero value `...Auto`) picks a polar map for the so
|
||||
|
||||
| Projection | Constant (solar/lunar - occultation) | When to use | Suggested canvas |
|
||||
| --- | --- | --- | --- |
|
||||
| Automatic | `EclipseMapProjectionAuto` - `MapProjectionAuto` | Default; picks a polar map per event | 1200x800 |
|
||||
| Equirectangular | `...Equirectangular` | Long bands crossing the antimeridian | 1200x800, 1414x1000 |
|
||||
| North/south polar equidistant | `...NorthPolar` / `...SouthPolar` | Event lies entirely at high latitude | 1200x800, 1000x1414 |
|
||||
| Orthographic globe | `...Orthographic` | NASA-style hemisphere view centred on the event | 1000x1414 |
|
||||
| Automatic | `EclipseMapProjectionAuto` - `MapProjectionAuto` | Default; picks a polar map per event | 1200×800 |
|
||||
| Equirectangular | `...Equirectangular` | Long bands crossing the antimeridian | 1200×800, 1414×1000 |
|
||||
| North/south polar equidistant | `...NorthPolar` / `...SouthPolar` | Event lies entirely at high latitude | 1200×800, 1000×1414 |
|
||||
| Orthographic globe | `...Orthographic` | NASA-style hemisphere view centered on the event | 1000×1414 |
|
||||
|
||||
`EclipseMapProjectionOrthographic` / `MapProjectionOrthographic` produce the orthographic globe: the view point is the greatest-eclipse point (the event centre for an occultation), only the hemisphere facing it is drawn, and the projection boundary is the great circle of the visible hemisphere.
|
||||
`EclipseMapProjectionOrthographic` / `MapProjectionOrthographic` produce the orthographic globe: the view point is the greatest-eclipse point (the event center for an occultation), only the hemisphere facing it is drawn, and the projection boundary is the great circle of the visible hemisphere.
|
||||
|
||||
The orthographic projection uses the NASA-style centered-globe layout; a `1000x1414` canvas is recommended. It does not change the underlying geographic results.
|
||||
The orthographic projection uses the NASA-style centered-globe layout; a `1000×1414` canvas is recommended. It does not change the underlying geographic results.
|
||||
|
||||
Four projections of one event in a single pass:
|
||||
|
||||
@@ -210,9 +239,7 @@ for _, spec := range []struct {
|
||||
}
|
||||
```
|
||||
|
||||
Per-family options, layer switches and illustrated examples live in the [solar and lunar eclipse manual](eclipse.md#solar-and-lunar-eclipse-charts) and the [lunar occultation manual](occultation.md#lunar-occultation-charts); the GeoJSON side carries geographic results only and leaves the projection to the application.
|
||||
|
||||
Degradable layers record their actual geometry source in `data-source`; the vocabulary is listed in the `eclipse/svg` package comment: `partial-band-union`, `sampled-footprint-sweep`, `partial-band-contours`, `rise-set-phase-lines`, `magnitude-contours`, `greatest-time-isochrones`, `besselian-critical-envelope`, `paired-limit-chords`, `sampled-open-sweep`, `central-path-limits`, `penumbral-outlines`, `central-shadow-outlines`, `p1-p4-visibility-regions`, `p1-p4-horizon-boundaries`.
|
||||
Per-family options, layer switches and illustrated examples live in the [solar, lunar and transit eclipse manual](eclipse.md#solar-and-lunar-eclipse-charts) and the [lunar occultation manual](occultation.md#lunar-occultation-charts); the GeoJSON side carries geographic results only and leaves the projection to the application. Transit visibility regions, C1/C4 apparent horizons and optional topocentric isochrones can also be exported as GeoJSON; see the transit export example above.
|
||||
|
||||
## Time Scale Declaration
|
||||
|
||||
@@ -233,7 +260,7 @@ Time markers are placed at whole clock ticks of the output scale (a UT1 tick is
|
||||
|
||||
`geojson` accepts an already computed solar-eclipse, lunar-eclipse, or lunar-occultation result and returns `[]byte`.
|
||||
|
||||
Those bytes are one complete UTF-8 RFC 7946 `FeatureCollection`, not an image or compressed payload: they can be written to `.geojson`, passed to `encoding/json`, or served directly to a map client.
|
||||
Those bytes are one complete UTF-8 RFC 7946 `FeatureCollection`: they can be written directly to `.geojson`, or handed to `encoding/json` and a front-end map component.
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -282,9 +309,10 @@ Each event type has plain and time-marker variants:
|
||||
|
||||
- `MarshalSolarEclipse` / `MarshalSolarEclipseWithTimeMarkers`
|
||||
- `MarshalLunarEclipse` / `MarshalLunarEclipseWithTimeMarkers`
|
||||
- `MarshalLunarEclipseWithOptions`: one `LunarEclipseOptions` value carrying the time markers, `SkipRoles` and the time-envelope sampling. `SkipRoles` can omit envelopes and penumbra-only bands; skipping both envelopes also skips their sampling. `EnvelopeSweepSamples` defaults to 48 and is clamped to `[2, 192]`; `EnvelopeLongitudePoints` defaults to `max(360, boundaryPoints)` and is clamped to `[12, 720]`.
|
||||
- `MarshalLunarEclipseWithOptions`: one `LunarEclipseOptions` value carrying the time markers, `SkipRoles` and the time-envelope sampling parameters. `SkipRoles` can omit envelopes and penumbra-only bands; skipping both envelopes also skips their sampling. `EnvelopeSweepSamples` defaults to 48 and is clamped to `[2, 192]`; `EnvelopeLongitudePoints` defaults to `max(360, boundaryPoints)` and is clamped to `[12, 720]`.
|
||||
- `MarshalStarOccultation` / `MarshalStarOccultationWithTimeMarkers`
|
||||
- `MarshalPlanetOccultation` / `MarshalPlanetOccultationWithTimeMarkers`
|
||||
- `MarshalMercuryTransit` / `MarshalVenusTransit`: take `mercury.TransitInfo` / `venus.TransitInfo` and partition the globe like the transit SVG map (whether the Sun's center is above the apparent horizon, standard refraction, at C1, greatest and C4). `BoundaryPoints` is the horizon sample count and also sets the region longitude columns to `max(360, BoundaryPoints)`, capped at 720. Roles are the three regions `transit-entire`, `transit-at-sunrise` and `transit-at-sunset`, the visible hemispheres and horizons `visible-at-c1` / `visible-at-c4` and `c1-horizon` / `c4-horizon`, `greatest` (the subsolar point) and the time markers `time-marker`. A positive `IsochroneStep` (at least 1 s) adds topocentric `c1-isochrone` through `c4-isochrone` (no C2/C3 for a grazing transit; the times are topocentric contacts and parallax can place them outside the geocentric C1–C4 interval), and a contact whose isochrone does not reach the visible hemisphere omits that role; skipping all three region roles skips the region computation.
|
||||
- `MarshalSolarEclipseWithOptions`: one `SolarEclipseOptions` value carrying both the time markers and `SkipRoles`, equivalent to the solar entry points above plus layer trimming.
|
||||
|
||||
The usual entry in `SkipRoles` is `partial-footprint` (the instantaneous penumbral outlines); `partial-band` is the edge of the eclipse band (the magnitude-0 limit) and is normally kept.
|
||||
@@ -352,7 +380,7 @@ Prior single-machine measurements put a 96-point footprint at about 64 µs and a
|
||||
|
||||
Draw `central-shadow-boundary` for the physical edge and fill the footprint for the region. At U1/U4, an empty footprint is omitted rather than degraded to a line. `source_boundary_closed=true` means the physical boundary closes by itself.
|
||||
|
||||
Sampled penumbral footprints carry the same properties. A footprint describes one instant; a static band describes the event-wide envelope. They are not interchangeable.
|
||||
Sampled penumbral footprints carry the same properties. A footprint covers only one instant and a static band covers the whole event, so do not mix them when taking geometry.
|
||||
|
||||
Compare adjacent `interp_signature` values, such as `umbra-closed-seg1-pt97`, before interpolating vertices. Signatures, segment counts and vertex counts must match. This condition is not a strict interpolation error bound.
|
||||
|
||||
@@ -380,7 +408,7 @@ Occultations use process-wide ΔT and report its actual value.
|
||||
|
||||
`eclipse.SolarEclipseCandidates(start, end, options)` returns greatest times, eclipse types, central types, magnitudes, gamma and optional Saros information. It does not calculate geographic geometry.
|
||||
|
||||
For central eclipses at a fixed site, `SearchLocalCentralSolarEclipse` accepts `Kind`, `MaxYears`, `Backward`, `Geometric` and `Model` options and returns `(info, status)`. `status.Exhausted` reports that the search range was exhausted.
|
||||
For central eclipses at a fixed site, `SearchLocalCentralSolarEclipse` accepts `Kind`, `MaxYears`, `Backward`, `Geometric` and `Model` options and returns `(info, SolarEclipseLocalSearchStatus)`, whose `Exhausted` field reports that the search range was exhausted.
|
||||
|
||||
`MaxYears<=0` uses the default budget of 6000 candidate steps, about 992 years. Calculate paths, footprints or SVG only after selecting an event when full maps are not needed for every candidate.
|
||||
|
||||
@@ -444,6 +472,7 @@ Features are grouped into Folders by `event` and `role`. Collections containing
|
||||
| Total and partial occultation bands | `total-band`, `partial-band`, `total-band-outline`, `occultation-band` | Yellow with translucent band fill |
|
||||
| Rise/set visibility edges, lunar time envelopes and solar partial-band edge | `visibility-boundary`, `p1-horizon`, `p4-horizon`, `visible-at-p1`, `visible-at-p4`, `visible-during-eclipse`, `visible-throughout-eclipse`, solar `partial-band` | Orange |
|
||||
| Penumbra-only moonrise/moonset bands | `penumbra-moonrise`, `penumbra-moonset` | Blue-violet for moonrise, violet for moonset, with a translucent fill |
|
||||
| Transit visibility regions and contact lines | `transit-entire`, `transit-at-sunrise`, `transit-at-sunset`, `c1-horizon`, `c4-horizon`, `visible-at-c1`, `visible-at-c4`, `c1-isochrone` … `c4-isochrone` | Each of the three regions has its own fill; C1/C4 horizons and visible hemispheres are orange; the four contact isochrones each get a color |
|
||||
| Other limits, outlines, footprints and time markers | Other roles | Grey; polygons are outline-only by default |
|
||||
|
||||
Zero-valued `Style` fields retain palette defaults:
|
||||
|
||||
+101
-25
@@ -31,10 +31,12 @@ Targets use two distinct contact models:
|
||||
- [Path algorithms and contours](#path-algorithms-and-contours)
|
||||
- [Greatest-time contours](#greatest-time-contours)
|
||||
- [Planetary occultations](#planetary-occultations)
|
||||
- [Lunar limb correction](#lunar-limb-correction)
|
||||
- [Lunar Occultation Charts](#lunar-occultation-charts)
|
||||
- [Global paths](#global-paths)
|
||||
- [Detailed layout](#detailed-layout)
|
||||
- [Fixed-site charts](#fixed-site-charts)
|
||||
- [Precise limb-profile chart](#precise-limb-profile-chart)
|
||||
- [Time scale and UT1](#time-scale-and-ut1)
|
||||
|
||||
## Searching for a stellar occultation at a fixed site
|
||||
@@ -92,6 +94,8 @@ The snippets use the target and search window from the first example. SVG calls
|
||||
| `moonsvg.FindLocalStarOccultationSVGs` / `FindLocalPlanetOccultationSVGs` | Search and render fixed-site charts | Same |
|
||||
| `moonsvg.StarOccultationPathSVG` / `PlanetOccultationPathSVG` | Render an existing global path | Return `(string, error)` |
|
||||
| `moonsvg.StarOccultationDetailedSVG` / `PlanetOccultationDetailedSVG` | One-page detailed layout | Fixed orthographic globe |
|
||||
| `moon.StarOccultationLimbProfile` / `StarOccultationLimbProfileInUT1` | Precise solve along the star track with a limb profile, returning every hidden interval and the in-band samples | Needs a profile; result is a `StarOccultationLimbProfileResult` |
|
||||
| `moonsvg.StarOccultationLimbProfileSVG` | Precise limb-profile chart | Options are `StarOccultationLimbProfileSVGOptions` |
|
||||
| `moonsvg.StarOccultationSVGOptions` / `moonsvg.OccultationDetailedSVGOptions` | Chart options (canvas, projection, scale, marker step) | Projections via `MapProjection*`, scales via `astro.TimeScale*` |
|
||||
| `moon.OccultationMercury` ... `moon.OccultationNeptune` | Planetary target constants | Passed to the planetary entry points |
|
||||
| `moon.OccultationSearchOptions` / `moon.OccultationPathOptions` | Search and path options | Path options include `Step`, `TargetSpacingKM` and `GreatestTimeStep` |
|
||||
@@ -183,7 +187,7 @@ fmt.Println(err, len(svg), detailErr, len(detailed))
|
||||
|
||||
- Path maps return `(string, error)` and accept the same four projections as the solar maps; the detailed layout is fixed to the orthographic globe, accepts no other projection, and derives its arrangement from the canvas aspect.
|
||||
- Use `StarOccultationPathSVG` when the path already exists, and `FindStarOccultationSVGs` for search-plus-render in one call (see the two chains above).
|
||||
- Canvas floors: `640x480` for a standalone path map. The detailed layout accepts a width of `480` but in practice needs about `670x595` to hold the map, data blocks and footer; smaller canvases return an error.
|
||||
- Canvas floors: `640×480` for a standalone path map. The detailed layout accepts a width of `480` but in practice needs about `670×595` to hold the map, data blocks and footer; smaller canvases return an error.
|
||||
|
||||
### Grazing events, isochrones and band widths
|
||||
|
||||
@@ -309,10 +313,10 @@ Output:
|
||||
|
||||
```text
|
||||
进贤增九 total
|
||||
2025-06-05 19:14:01.076 CST 2025-06-05 20:02:06.311 CST 2025-06-05 20:50:10.721 CST
|
||||
altitude=75.561 visible=true
|
||||
2025-06-05 17:45:28.475 CST 2025-06-05 20:02:06.300 CST 2025-06-05 22:18:49.945 CST
|
||||
greatest=121.566009 6.807079 width=3582.4km center=108
|
||||
2025-06-05 19:14:03.127 CST 2025-06-05 20:02:08.397 CST 2025-06-05 20:50:12.789 CST
|
||||
altitude=75.556 visible=true
|
||||
2025-06-05 17:45:29.822 CST 2025-06-05 20:02:07.622 CST 2025-06-05 22:18:51.246 CST
|
||||
greatest=121.561060 6.808173 width=3582.4km center=108
|
||||
```
|
||||
|
||||
### Search and path sampling options
|
||||
@@ -333,11 +337,7 @@ The result's `GreatestLimitSeparationKM` is the ground separation between northe
|
||||
|
||||
### Path algorithms and contours
|
||||
|
||||
`OccultationPathOptions.Algorithm` selects the stellar/planetary global-path ephemeris branch.
|
||||
|
||||
Its zero value or `moon.OccultationPathAlgorithmOptimized` uses checked Cartesian interpolation at 30-minute nodes while retaining the station equations, continuous envelopes, and rise/set curves.
|
||||
|
||||
`moon.OccultationPathAlgorithmExact` uses interpolated candidates with full-term ephemerides for final solving. Failed table checks fall back to full-term solving; evaluations outside the interpolation window use exact ephemerides.
|
||||
`OccultationPathOptions.Algorithm` selects the stellar/planetary global-path ephemeris branch: its zero value or `moon.OccultationPathAlgorithmOptimized` approximates the ephemeris with vector interpolation at 30-minute nodes, and recomputes with the full ephemeris when the interpolation self-check fails or the instant falls outside the interpolation window. `moon.OccultationPathAlgorithmExact` interpolates only during candidate screening and always solves with the full ephemeris. Both branches keep the same station equations, band boundaries and rise/set curves.
|
||||
|
||||
Checks are sampled safeguards, not a rigorous error bound at every instant. The branches share the geometric definition, but sample points and GeoJSON bytes need not be identical.
|
||||
|
||||
@@ -406,17 +406,54 @@ Output:
|
||||
|
||||
```text
|
||||
Saturn total true
|
||||
2025-02-01 11:29:09.710 CST
|
||||
2025-02-01 11:29:40.069 CST
|
||||
2025-02-01 12:00:48.747 CST
|
||||
2025-02-01 12:32:46.415 CST
|
||||
2025-02-01 12:33:18.312 CST
|
||||
2025-02-01 11:29:10.984 CST
|
||||
2025-02-01 11:29:41.333 CST
|
||||
2025-02-01 12:00:50.058 CST
|
||||
2025-02-01 12:32:47.753 CST
|
||||
2025-02-01 12:33:19.650 CST
|
||||
```
|
||||
|
||||
Global `FindPlanetOccultationPaths` results contain both the region where any part of the planetary disk overlaps the Moon and the region where the whole planet is hidden.
|
||||
|
||||
`HasTotalBand` reports whether a total band exists, and `GreatestTotalWidthKM` is its width at greatest occultation; center lines, limits, and enabled instantaneous footprints all carry sample times.
|
||||
|
||||
## Lunar limb correction
|
||||
|
||||
The Moon is a circular disk by default. Stellar and planetary occultation searches at a fixed site can use the ISAS/JAXA limb profile from the `moon/limb` package (a processed product of SELENE/Kaguya LALT data; see the [eclipse and transit manual](eclipse.md#lunar-limb-correction)): disappearance, reappearance and the planet's inner and outer contacts are refined with the actual limb radius toward the target. Greatest occultation, type and candidate screening still use the circular disk.
|
||||
|
||||
```go
|
||||
import "b612.me/astro/moon/limb"
|
||||
|
||||
events, err := moon.FindStarOccultations(start, end, star, lon, lat, 0,
|
||||
moon.OccultationSearchOptions{LunarLimb: limb.Default()})
|
||||
|
||||
// Or set it once on a context
|
||||
base, err := astro.NewContext(astro.ModelSemianalytic)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx := base.WithLunarLimb(limb.Default())
|
||||
events, err = ctx.Moon().FindStarOccultations(start, end, star, lon, lat, 0, moon.OccultationSearchOptions{})
|
||||
```
|
||||
|
||||
Corrections are usually a few seconds; for the 2024-08-21 occultation of Saturn the contacts move by `-1.8 s` to `-0.6 s`.
|
||||
|
||||
Accuracy limits:
|
||||
|
||||
- The built-in profile is interpolated on a 1° libration grid; the limb margin at a single contact is good to about `±0.05″` (about 100 m on the Moon), set by the 16 pixel/degree terrain and the interpolation together.
|
||||
- In a graze the star skims the limb with a margin of a few hundredths of an arcsecond, so the number of hidden intervals and the brief disappearances and reappearances can differ from observations; only the main interval and its magnitude are reliable. Example: the Regulus graze of 2017-09-18 (Greece, 5 intervals observed by IOTA) gives 1 interval here, whose disappearance and reappearance differ from the last observed interval by `+0.6 s` / `-0.4 s`.
|
||||
- For finer graze profiles, build a table from higher-resolution terrain and load it with `limb.Parse`, or implement the `LunarLimb` interface.
|
||||
|
||||
Global occultation paths (`FindStarOccultationPaths`, `FindPlanetOccultationPaths`, via `OccultationPathOptions.LunarLimb` or the module setting) correct only the outer-contact northern and southern limits:
|
||||
|
||||
- The limits move onto the limb graze lines: the star track is treated as straight near the tangent point and the effective limb is the highest point within ±8° of position angle.
|
||||
- Points with the Moon near the horizon stay circular.
|
||||
- Planetary total-occultation limits, band fills, isochrones and rise/set curves stay circular.
|
||||
- Multiple disappearances and reappearances inside the graze zone are not computed here.
|
||||
- For HR 4799 on 2025-06-05 the graze lines move by up to about `4 km`.
|
||||
|
||||
Charts need no extra switch: `LunarLimbApplied` on the result records whether the limb correction actually took effect, and `moon/svg` declares the convention after the time-scale note. A local result sets it only when every contact converged on the profile; if any contact fails, the whole result stays circular. A path sets it when at least one limit point was corrected. Converged contacts no longer sit on the circular limb, so local charts skip the circular contact check for them and render events from the built-in or a custom profile directly.
|
||||
|
||||
## Lunar Occultation Charts
|
||||
|
||||
`moon/svg` offers two groups of entry points: "search and render" and "render an existing result".
|
||||
@@ -428,6 +465,7 @@ Global `FindPlanetOccultationPaths` results contain both the region where any pa
|
||||
| `StarOccultationDetailedSVG` / `PlanetOccultationDetailedSVG` | One-page detailed layout |
|
||||
| `FindLocalStarOccultationSVGs` / `FindLocalPlanetOccultationSVGs` | Fixed-site topocentric lunar track, lunar path and contact-phase charts |
|
||||
| `LocalStarOccultationSVG` / `LocalPlanetOccultationSVG` | Render an existing fixed-site event |
|
||||
| `StarOccultationLimbProfileSVG` | Precise limb-profile chart: limb relief along the track, every hidden interval and the immersion/reappearance numbering |
|
||||
|
||||
`eclipse/svg` entry points return `(string, bool)` while `moon/svg` entry points return `(string, error)` or `([]string, error)`: on the occultation side the second value is a real error (UT1 with a non-UTC location, canvas below the floor, invalid path), not a "cannot draw this chart" flag.
|
||||
|
||||
@@ -445,8 +483,8 @@ Greatest instants are measured from the Sun-Moon center separation for point sta
|
||||
| --- | --- |
|
||||
| `MapProjectionAuto` (zero value) | Chosen per event; high-latitude events may land on a polar map |
|
||||
| `MapProjectionEquirectangular` | Equirectangular, clearest for long paths crossing the antimeridian |
|
||||
| `MapProjectionNorthPolar` / `MapProjectionSouthPolar` | Azimuthal equidistant with the pole at the centre |
|
||||
| `MapProjectionOrthographic` | Orthographic globe viewed from the event centre, drawing only the hemisphere facing it |
|
||||
| `MapProjectionNorthPolar` / `MapProjectionSouthPolar` | Azimuthal equidistant with the pole at the center |
|
||||
| `MapProjectionOrthographic` | Orthographic globe viewed from the event center, drawing only the hemisphere facing it |
|
||||
|
||||
The orthographic globe (`2025-06-05` occultation of HR 4799):
|
||||
|
||||
@@ -493,15 +531,15 @@ svgs, err := moonsvg.FindStarOccultationSVGs(start, end, target,
|
||||
fmt.Println(err, len(svgs))
|
||||
```
|
||||
|
||||
`TimeLabelStep` defaults to 30 minutes and a negative value disables the time markers along the center line. A standalone global path map needs at least `640x480`; smaller canvases return `ErrInvalidStarOccultationSVGOptions` (`ErrInvalidPlanetOccultationSVGOptions` for the planetary entry points).
|
||||
`TimeLabelStep` defaults to 30 minutes and a negative value disables the time markers along the center line. A standalone global path map needs at least `640×480`; smaller canvases return `ErrInvalidStarOccultationSVGOptions` (`ErrInvalidPlanetOccultationSVGOptions` for the planetary entry points).
|
||||
|
||||
The legend, footer and graticule all reserve space by canvas height, so shorter canvases press against the footer more easily; the south-polar example in this manual works fine at `1200x800`.
|
||||
The legend, footer and graticule all reserve space by canvas height, so shorter canvases press against the footer more easily; the south-polar example in this manual works fine at `1200×800`.
|
||||
|
||||
### Detailed layout
|
||||
|
||||
The detailed layout combines a whole occultation on one `1000x1414` page: a centred summary, geocentric/topocentric data blocks for Moon and target, an **orthographic globe** path map (northern and southern limits, visible/geometric center lines, the greatest point, immersion/greatest/emersion phase points and 30-minute time markers), and a footer note.
|
||||
The detailed layout combines a whole occultation on one `1000×1414` page: a centered summary, geocentric/topocentric data blocks for Moon and target, an **orthographic globe** path map (northern and southern limits, visible/geometric center lines, the greatest point, immersion/greatest/emersion phase points and 30-minute time markers), and a footer note.
|
||||
|
||||
The globe is viewed from the event centre and this layout is fixed to the orthographic projection, accepting no other; use the `StarOccultationPathSVG` / `FindStarOccultationSVGs` above when only a standalone path map is needed.
|
||||
The globe is viewed from the event center and this layout is fixed to the orthographic projection, accepting no other; use the `StarOccultationPathSVG` / `FindStarOccultationSVGs` above when only a standalone path map is needed.
|
||||
|
||||
```go
|
||||
detailed, err := moonsvg.StarOccultationDetailedSVG(paths[0], target,
|
||||
@@ -519,15 +557,15 @@ A landscape canvas puts the data blocks in two columns by three rows to the righ
|
||||
|
||||
The globe path map uses the Natural Earth `1:50m` coastline without administrative boundaries.
|
||||
|
||||
The detailed layout derives its arrangement from the canvas and needs about `670x595` in practice (the `480` width floor is only an argument check), returning `ErrInvalidOccultationDetailedSVGOptions` when the map and data blocks do not fit (`800x600`, `1000x1414` and `1414x1000` all render; `660x600`, `800x590`, `640x420` and `900x400` are rejected).
|
||||
The detailed layout derives its arrangement from the canvas and needs about `670×595` in practice (the `480` width floor is only an argument check), returning `ErrInvalidOccultationDetailedSVGOptions` when the map and data blocks do not fit (`800×600`, `1000×1414` and `1414×1000` all render; `660×600`, `800×590`, `640×420` and `900×400` are rejected).
|
||||
|
||||
The "band width" printed on the map is the ground separation of the northern and southern limits at greatest occultation, `GreatestLimitSeparationKM` (about `3666.6 km` for the HR 4799 sample), which is a different convention from the across-center-line width `Greatest.WidthKM` (about `3582.4 km`); the two are not interchangeable.
|
||||
The "band width" printed on the map is the ground separation of the northern and southern limits at greatest occultation, `GreatestLimitSeparationKM`, not the across-center-line width `Greatest.WidthKM`; the two are not interchangeable.
|
||||
|
||||
Greatest-time isochrones must be requested explicitly at the path layer through `moon.OccultationPathOptions.GreatestTimeStep`: `moon/svg` offers no switch of its own and only draws the `GreatestTimeContours` already present in the path result, so the request has to be made while computing the path and the line positions follow the core convention (`GreatestTimeStep` aligns to whole UTC marks; to align to the display time zone, convert the instants to dynamical-time Julian days first and pass them as `GreatestTimeValues`).
|
||||
|
||||
A lunar occultation is visible worldwide for only a few hours (4 h 33 min for the HR 4799 sample), so the usual interval is denser than for solar eclipses, on the order of 15-30 minutes.
|
||||
|
||||
A compact band using `DisableFootprints` merges once on the first render and then caches for the same path.
|
||||
A compact band using `DisableFootprints` is computed only once on the first render, and the same path reuses the cache afterwards.
|
||||
|
||||
### Fixed-site charts
|
||||
|
||||
@@ -543,10 +581,48 @@ fmt.Println(err, len(localSVGs))
|
||||
|
||||
The local chart is drawn from the topocentric geometry of the given observer; the figure below reuses the `2025-06-05` occultation of Xianxianzengjiu (HR 4799).
|
||||
|
||||
The observer is at `121.56601 E, 6.80706 N`, close to the global geometric greatest point; the immersion, greatest and emersion instants in the figure are the topocentric contacts actually seen from that site, together with lunar orientation, the lunar path, Moon altitude, azimuth and horizon visibility.
|
||||
The chart's observer is at `121.56601°E, 6.80706°N`, close to the global geometric greatest point; the immersion, greatest and emersion instants in the figure are the topocentric contacts actually seen from that site, together with lunar orientation, the lunar path, Moon altitude, azimuth and horizon visibility.
|
||||
|
||||

|
||||
|
||||
### Precise limb-profile chart
|
||||
|
||||
The contact times above are solved on a circular limb. Feeding the limb relief in point by point gives the limb geometry at every instant along the star track and **every** hidden interval: a deep occultation has one disappearance and one reappearance segment, a grazing event usually just one, and a limb valley can split a single occultation into several separate intervals.
|
||||
|
||||
```go
|
||||
profile, err := moon.StarOccultationLimbProfile(events[0], target, limb.Default(),
|
||||
moon.StarOccultationLimbProfileOptions{Step: 200 * time.Millisecond})
|
||||
fmt.Println(len(profile.Hidden), profile.Hidden[0].Start.UTC(), profile.Hidden[len(profile.Hidden)-1].End.UTC())
|
||||
samples := 0
|
||||
for _, segment := range profile.Segments {
|
||||
samples += len(segment)
|
||||
}
|
||||
fmt.Println(profile.Step, len(profile.Segments), samples)
|
||||
|
||||
profileSVG, err := moonsvg.StarOccultationLimbProfileSVG(profile,
|
||||
moonsvg.StarOccultationLimbProfileSVGOptions{Width: 1000, Height: 620, Location: cst, Language: "en"})
|
||||
fmt.Println(err, len(profileSVG))
|
||||
```
|
||||
|
||||
```text
|
||||
1 2025-06-05 11:13:59.65061903 +0000 UTC 2025-06-05 12:50:11.632025241 +0000 UTC
|
||||
200ms 2 191
|
||||
<nil> 30702
|
||||
```
|
||||
|
||||

|
||||
|
||||
The horizontal axis is time, each tick also labelled with the limb position angle at that instant, and the vertical axis is height above the reference circular limb in kilometres. The brown solid curve is the limb radius toward the star, the red dashed curve is the star's angular distance from the Moon's center, and their crossings are the hidden intervals, shaded and numbered; one panel is drawn per entry in `Segments`, with a sky-plane inset and the immersion/reappearance list on the right. The circular-limb result for the same event (`11:14:03.127` / `12:50:12.789 UTC`) differs by a few seconds — exactly the limb-relief correction.
|
||||
|
||||
Conventions:
|
||||
|
||||
- `info` contributes only the instant of greatest occultation and the observer; the remaining contacts are re-solved on the profile and returned in `profile.Occultation`. It must be a UTC-based result — never pass a `...InUT1` copy.
|
||||
- A `nil` limb falls back to the module or context profile (`WithLunarLimb`); when neither is set the call returns `ErrInvalidOccultationInput`.
|
||||
- `Step` defaults to 200 ms and is raised to a 10 ms minimum; each segment keeps at least 64 samples and the total is capped at 20000, with the step adjusted automatically. **Hidden intervals shorter than the step can be missed**, so shrink the step for grazing detail.
|
||||
- `Segments` holds the samples inside the limb relief band (two for a deep occultation, usually one for a graze), `Hidden` holds every hidden interval, `ReliefMinKM` / `ReliefMaxKM` bound the limb radii used for gating, and `ReferenceRadiusKM` is the lunar radius behind `MeanRadiusArcsec`.
|
||||
- The chart defaults to `920×620` and needs at least `640×480`; anything smaller returns `ErrInvalidStarOccultationLimbProfileSVGOptions`, and a track that never enters the relief band returns `ErrEmptyStarOccultationLimbProfile`.
|
||||
- For UT1 labels take a copy with `moon.StarOccultationLimbProfileInUT1(profile)`, or pass `TimeScale: astro.TimeScaleUT1` to the chart options (then `Location` must be UTC).
|
||||
|
||||
### Time scale and UT1
|
||||
|
||||
Occultation charts are drawn in the UTC scale by default and state it in the figure.
|
||||
|
||||
+23
-8
@@ -4,7 +4,22 @@
|
||||
|
||||
`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.
|
||||
The seven major planets use the selected ephemeris through their own packages: default builds default to the semianalytic model, with Legacy or custom engines available explicitly.
|
||||
|
||||
Use `StateWithOptions` to propagate to a target epoch with optional planetary perturbations:
|
||||
|
||||
| `PropagationOptions` field | Meaning |
|
||||
| --- | --- |
|
||||
| `Perturbers` | Perturbing bodies; a non-empty value also requires an ephemeris source |
|
||||
| `Engine` / `Context` | The perturbation ephemeris source. Both fields have the same type (`StateProvider`, i.e. `astro.Engine`) and `Engine` wins; pass an `*spk.File` or a custom engine |
|
||||
| `Step` | Maximum step in days |
|
||||
| `Tolerance` | Relative step-doubling error threshold |
|
||||
|
||||
- The zero-value options struct follows the existing two-body path exactly, matching `State`.
|
||||
- The integrator adds solar gravity plus direct and indirect perturbation terms. `Perturbers` accepts Mercury through Neptune, Earth, the Moon and the Earth-Moon barycenter (3); an unknown body, or the barycenter combined with Earth or the Moon, returns `ErrUnsupportedPerturber`.
|
||||
- Integration starts at `EpochJD`; a perihelion-form set starts at `TpJD` only when `EpochJD` is 0.
|
||||
- `NewEphemerisEngine` adapts the propagated result for `astro.NewStack`: elements and options are fixed at construction, and the engine's cached checkpoints never change results, so any query order matches a direct `StateWithOptions` call bit for bit.
|
||||
- Accuracy depends on the supplied elements and propagation span; use SPK for long-term high-precision work.
|
||||
|
||||
`orbit.Elements` supports two common forms:
|
||||
|
||||
@@ -39,7 +54,7 @@ The positional and topocentric quantities here also work together with the inter
|
||||
- [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)
|
||||
- [Related manuals](#related-manuals)
|
||||
|
||||
## Calculating the position of Ceres from orbital elements
|
||||
|
||||
@@ -146,8 +161,8 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
ceres ra=7.739532 dec=-10.625981 distance=2.164391
|
||||
halley ra=312.112360 dec=-11.826451 distance=1.533936
|
||||
ceres ra=7.743516 dec=-10.625287 distance=2.164391
|
||||
halley ra=312.106763 dec=-11.828059 distance=1.533936
|
||||
```
|
||||
|
||||
Orbital elements have epochs. The farther the target date is from the epoch, the more static-element error can grow.
|
||||
@@ -215,7 +230,7 @@ fmt.Printf("r=%.6f delta=%.6f elong=%.6f phase=%.6f k=%.6f mag=%.3f q=%.6f\n",
|
||||
|
||||
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.
|
||||
`ParallacticAngle` does not solve for declination separately: it reuses the same topocentric solve as `HourAngle`, so the two are exactly self-consistent.
|
||||
|
||||
All geometry helpers depend only on the absolute instant of `date`; `ParallacticAngle` is the only one that also takes observer parameters.
|
||||
|
||||
@@ -338,7 +353,7 @@ fmt.Println(app.RA, app.Dec, app.Distance)
|
||||
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.
|
||||
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` adds 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)`.
|
||||
|
||||
@@ -431,7 +446,7 @@ NaN NaN
|
||||
- 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.
|
||||
- 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. Perturbed propagation is the exception: when a perihelion-form set also gives `EpochJD`, that epoch is the osculating epoch where integration starts.
|
||||
- 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.
|
||||
@@ -452,7 +467,7 @@ NaN NaN
|
||||
- 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
|
||||
## Related manuals
|
||||
|
||||
- Stars and catalogues: [star](star.md#stars)
|
||||
- Frame conversion and topocentric quantities: [coordinate tools](coord.md#coordinate-tools)
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
# Generic sky events and heliacal visibility
|
||||
|
||||
[中文](../phenomena.md) | [Back to README](../../../README.en.md)
|
||||
|
||||
The `phenomena` package searches events between any two bodies by geocentric apparent ecliptic longitude and latitude, computes morning and evening first and last visibility for planets and stars, and additionally provides rise and set at any altitude plus perihelion, aphelion and node passages. Package-level entry points use the process's current ephemeris model, and `ctx.Phenomena()` can fix an ephemeris instance; only a star's rise and set at any altitude is computed from the apparent coordinates passed in and does not read the ephemeris.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Quick example](#quick-example)
|
||||
- [Pairwise events](#pairwise-events)
|
||||
- [Star coordinates](#star-coordinates)
|
||||
- [Heliacal visibility](#heliacal-visibility)
|
||||
- [Rise and set at any altitude](#rise-and-set-at-any-altitude)
|
||||
- [Apsides and nodes](#apsides-and-nodes)
|
||||
- [API reference](#api-reference)
|
||||
- [Parameter and return conventions](#parameter-and-return-conventions)
|
||||
|
||||
## Quick example
|
||||
|
||||
```go
|
||||
start := time.Date(2020, 10, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2021, 3, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
events := phenomena.ClosestApproaches(phenomena.Jupiter, phenomena.Saturn, 1, start, end)
|
||||
fmt.Println(events[0].Time.UTC().Format("2006-01-02 15:04"), fmt.Sprintf("%.4f", events[0].Value))
|
||||
```
|
||||
|
||||
```text
|
||||
2020-12-21 18:21 0.1018
|
||||
```
|
||||
|
||||
`ClosestApproaches` returns the **local minima** of the angular separation of the two bodies within the window, where `Value` is the separation at that instant in degrees. It returns an empty slice when the window contains no local minimum below `maxSeparation`.
|
||||
|
||||
To fix the ephemeris instance, use the module method instead:
|
||||
|
||||
```go
|
||||
ctx, err := astro.NewContext(astro.ModelLegacy)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
events = ctx.Phenomena().ClosestApproaches(phenomena.Jupiter, phenomena.Saturn, 1, start, end)
|
||||
```
|
||||
|
||||
## Pairwise events
|
||||
|
||||
| Entry | Event | `Event.Value` |
|
||||
| --- | --- | --- |
|
||||
| `LongitudeCrossings(body, lon, start, end)` | apparent longitude passes through `lon` | apparent longitude at the event instant (degrees) |
|
||||
| `Stations(body, start, end)` | stations of apparent longitude (planets) | apparent longitude at the station (degrees) |
|
||||
| `AspectCrossings(a, b, angle, start, end)` | `lon(a) - lon(b) = angle`; `0` is conjunction in longitude, `180` opposition | the actual longitude difference (degrees) |
|
||||
| `ClosestApproaches(a, b, max, start, end)` | local minima of the angular separation of two bodies | angular separation (degrees) |
|
||||
| `StarApproaches(body, star, max, start, end)` | local minima of the angular separation from a star | angular separation (degrees) |
|
||||
|
||||
Each entry accepts only the parameters listed in the table above, and results are returned in ascending time order. Stations defined by right ascension in the per-planet packages use a different convention from the longitude stations here, so the times can differ by several hours.
|
||||
|
||||
```go
|
||||
// first two Mercury stations of 2026
|
||||
stations := phenomena.Stations(phenomena.Mercury,
|
||||
time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2026, 4, 1, 0, 0, 0, 0, time.UTC))
|
||||
for _, e := range stations {
|
||||
fmt.Println(e.Time.UTC().Format("01-02 15:04"), fmt.Sprintf("%.3f", e.Value))
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
02-26 06:48 352.565
|
||||
03-20 19:32 338.491
|
||||
```
|
||||
|
||||
```go
|
||||
// apparent solar longitude passing through 0° (vernal equinox)
|
||||
crossings := phenomena.LongitudeCrossings(phenomena.Sun, 0,
|
||||
time.Date(2026, 3, 18, 0, 0, 0, 0, time.UTC), time.Date(2026, 3, 24, 0, 0, 0, 0, time.UTC))
|
||||
fmt.Println(crossings[0].Time.UTC().Format("2006-01-02 15:04"))
|
||||
```
|
||||
|
||||
```text
|
||||
2026-03-20 14:45
|
||||
```
|
||||
|
||||
## Star coordinates
|
||||
|
||||
Both the angular separation of a planet and a star and star heliacal visibility require a `StarCoordinate`; the embedded 9100-star catalog can be queried directly:
|
||||
|
||||
| Entry | Input |
|
||||
| --- | --- |
|
||||
| `StarCoordinateByName(name)` | catalog Chinese name, for example `"天狼星"` |
|
||||
| `StarCoordinateByHR(hr)` | HR number |
|
||||
| `StarCoordinateFromStarData(star)` | a catalog record such as one obtained from `star.StarDataByHR` |
|
||||
|
||||
```go
|
||||
star, err := phenomena.StarCoordinateByName("天狼星")
|
||||
fmt.Println(err, star.ID, fmt.Sprintf("%.6f %.6f %.2f %.2f", star.RA, star.Dec,
|
||||
star.ProperMotionRACosDecMasPerYear, star.ProperMotionDecMasPerYear))
|
||||
```
|
||||
|
||||
```text
|
||||
<nil> 天狼 101.287083 -16.716111 -553.00 -1205.00
|
||||
```
|
||||
|
||||
`RA` and `Dec` are in degrees, and `Epoch` and `Frame` are required; the proper-motion fields (`ProperMotionRACosDecMasPerYear` and friends) follow the catalog convention `dRA·cos(Dec)`, in milliarcseconds per year. `ParallaxMas` (or the equivalent `DistanceLightYear`) gives the distance, and `RadialVelocityKmPerSecond` gives the radial velocity; when the distance is missing the propagation is two-dimensional, and the radial velocity is included only when a distance is present.
|
||||
|
||||
## Heliacal visibility
|
||||
|
||||
`PlanetHeliacalEvents(body, start, end, lon, lat, options)` and `StarHeliacalEvents(star, magnitude, start, end, lon, lat, options)` scan the morning and evening intervals day by day while the solar altitude runs from -18° to 0°, derive the naked-eye limiting magnitude from a sky-brightness model, and compare it with the target apparent magnitude (Mallama & Hilton 2018 for planets). They return four kinds of event:
|
||||
|
||||
| Constant | Meaning |
|
||||
| --- | --- |
|
||||
| `HeliacalMorningFirst` | morning first visibility (heliacal rising) |
|
||||
| `HeliacalMorningLast` | morning last visibility |
|
||||
| `HeliacalEveningFirst` | evening first visibility |
|
||||
| `HeliacalEveningLast` | evening last visibility (heliacal setting) |
|
||||
|
||||
In `HeliacalEvent`, `Time` is the best visible instant of that twilight, `TargetAltitude` and `SunAltitude` are the geometric altitudes of the target and the Sun at that instant (degrees), and `Margin` is the limiting magnitude minus the target magnitude.
|
||||
|
||||
| `HeliacalOptions` | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `ExtinctionCoefficient` | `0.25` | atmospheric extinction, magnitudes per airmass |
|
||||
| `MinTargetAltitude` | `0.5` | minimum geometric altitude of the target (degrees) |
|
||||
| `Moonlight` | `false` | include scattered moonlight (Krisciunas-Schaefer) |
|
||||
|
||||
```go
|
||||
events := phenomena.PlanetHeliacalEvents(phenomena.Venus,
|
||||
time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC),
|
||||
31.23, 30.04, phenomena.HeliacalOptions{})
|
||||
for _, e := range events {
|
||||
fmt.Println(e.Kind, e.Time.UTC().Format("2006-01-02"), fmt.Sprintf("margin=%.2f", e.Margin))
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
3 2025-03-18 margin=0.10
|
||||
0 2025-03-25 margin=0.58
|
||||
1 2025-12-01 margin=0.10
|
||||
```
|
||||
|
||||
`Kind` prints as the constant's ordinal, so compare against the constants themselves when testing. `LimitingMagnitude(targetAltitude, sunAltitude, separation, extinction)` gives the limiting magnitude at a single instant on a moonless night, from the same source as the event test:
|
||||
|
||||
```go
|
||||
fmt.Println(fmt.Sprintf("%.3f", phenomena.LimitingMagnitude(20, -12, 60, 0.25)))
|
||||
```
|
||||
|
||||
```text
|
||||
3.408
|
||||
```
|
||||
|
||||
Results are sensitive to the extinction coefficient, and a spread of one or two days in the visible date is normal.
|
||||
|
||||
## Rise and set at any altitude
|
||||
|
||||
`AltitudeCrossing(body, date, lon, lat, height, altitude, options)` finds the rise and set times at which the Sun, Moon or a planet first crosses the apparent altitude `altitude` within the **local civil day** containing `date`. `StarAltitudeCrossing(date, ra, dec, lon, lat, altitude, options)` performs the same computation for a star's apparent coordinates; it does not read the ephemeris and has no module method.
|
||||
|
||||
| `AltitudeOptions` | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `Limb` | `LimbUpper` | upper limb for the Sun and Moon, or `LimbCenter` and `LimbLower`; ignored for planets and stars |
|
||||
| `PressureHPa` / `TemperatureC` | standard atmosphere (the `0` value) | specify measured weather |
|
||||
| `NoRefraction` | `false` | when `true`, compare geometric altitudes |
|
||||
|
||||
| `AltitudeCrossingResult` | Description |
|
||||
| --- | --- |
|
||||
| `Rise`, `Set` | rise and set times, zero when there is no event |
|
||||
| `HasRise`, `HasSet` | whether a rise or set occurs that day |
|
||||
| `State` | `AltitudeCrosses`, `AltitudeAlwaysAbove`, `AltitudeAlwaysBelow` or `AltitudeTangent` (the extremum differs from the target altitude by less than `1″`) |
|
||||
| `MinResidual`, `MaxResidual` | minimum and maximum of the altitude minus the target altitude that day (degrees) |
|
||||
|
||||
```go
|
||||
cst := time.FixedZone("CST", 8*3600)
|
||||
r, err := phenomena.AltitudeCrossing(phenomena.Sun, time.Date(2026, 3, 21, 12, 0, 0, 0, cst),
|
||||
116.4, 39.9, 0, -18, phenomena.AltitudeOptions{})
|
||||
fmt.Println(err, r.State, r.Rise.In(cst).Format("15:04:05"), r.Set.In(cst).Format("15:04:05"),
|
||||
fmt.Sprintf("%.3f %.3f", r.MinResidual, r.MaxResidual))
|
||||
```
|
||||
|
||||
```text
|
||||
<nil> 0 04:44:54 19:59:16 -31.806 68.602
|
||||
```
|
||||
|
||||
```go
|
||||
// 89°N polar day: the Sun stays above -18° all day
|
||||
polar, err := phenomena.AltitudeCrossing(phenomena.Sun, time.Date(2026, 6, 21, 12, 0, 0, 0, time.UTC),
|
||||
0, 89, 0, -18, phenomena.AltitudeOptions{})
|
||||
fmt.Println(err, polar.State, polar.HasRise, polar.HasSet, fmt.Sprintf("%.3f", polar.MinResidual))
|
||||
```
|
||||
|
||||
```text
|
||||
<nil> 1 false false 40.737
|
||||
```
|
||||
|
||||
```go
|
||||
// rise and set of Sirius through -0.833°
|
||||
sirius, err := phenomena.StarAltitudeCrossing(time.Date(2026, 1, 1, 12, 0, 0, 0, cst),
|
||||
star.RA, star.Dec, 116.4, 39.9, -0.833, phenomena.AltitudeOptions{Limb: phenomena.LimbCenter})
|
||||
fmt.Println(err, sirius.State, sirius.Rise.In(cst).Format("15:04:05"), sirius.Set.In(cst).Format("15:04:05"))
|
||||
```
|
||||
|
||||
```text
|
||||
<nil> 0 19:04:31 05:27:52
|
||||
```
|
||||
|
||||
`height` only affects lunar parallax. When a depressed horizon is required, the caller subtracts the horizon dip from `altitude`. When the body is not the Sun, Moon or a planet (for example `phenomena.Earth`), or a star's apparent coordinates are not finite, the function returns `basic.ErrInvalidObservationInput`.
|
||||
|
||||
## Apsides and nodes
|
||||
|
||||
| Constant | Event |
|
||||
| --- | --- |
|
||||
| `Perihelion`, `Aphelion` | perihelion, aphelion |
|
||||
| `AscendingNode`, `DescendingNode` | ascending and descending node passages through the mean ecliptic of epoch |
|
||||
|
||||
`Apsides(body, start, end)` accepts a planet or the Earth (`phenomena.Earth`), while `Nodes(body, start, end)` accepts only planets and returns an empty slice for the Earth. In `OrbitalEvent`, `Time` is the event instant, `Kind` is a constant from the table above, and `DistanceAU` is the heliocentric distance at that instant (AU).
|
||||
|
||||
```go
|
||||
apsides := phenomena.Apsides(phenomena.Earth,
|
||||
time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2027, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
for _, e := range apsides {
|
||||
fmt.Println(e.Kind, e.Time.UTC().Format("2006-01-02 15:04"), fmt.Sprintf("%.9f AU", e.DistanceAU))
|
||||
}
|
||||
nodes := phenomena.Nodes(phenomena.Jupiter,
|
||||
time.Date(2018, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
for _, e := range nodes {
|
||||
fmt.Println(e.Kind, e.Time.UTC().Format("2006-01-02 15:04"), fmt.Sprintf("%.6f AU", e.DistanceAU))
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
0 2026-01-03 09:15 0.983302049 AU
|
||||
1 2026-07-06 09:30 1.016643962 AU
|
||||
3 2020-02-25 18:09 5.207056 AU
|
||||
2 2025-09-19 11:58 5.174312 AU
|
||||
```
|
||||
|
||||
The extremum is taken on the heliocentric distance itself: the Sun's reflex motion about the barycenter makes the heliocentric distance of Uranus and Neptune show several small extrema near perihelion, so when several extrema appear in one span only the one with the smallest heliocentric distance within `0.3` orbital periods before and after is returned, which can differ from the osculating perihelion by several years.
|
||||
|
||||
## API reference
|
||||
|
||||
The methods of `ctx.Phenomena()` match the package-level functions of the same name (except `StarAltitudeCrossing`); `NewWithInstance` creates a module bound to a given ephemeris instance. Parameters and units are in the sections above.
|
||||
|
||||
## Parameter and return conventions
|
||||
|
||||
- Angles are always in degrees, `OrbitalEvent.DistanceAU` is in AU, and `magnitude` in `StarHeliacalEvents` is V magnitude.
|
||||
- Time parameters are interpreted as absolute instants (their UTC fields are read), and returned instants are converted to the time zone of `start` (`date` for the rise and set entries).
|
||||
- Event searches return an empty slice rather than an error when there is no result; only rise and set at any altitude returns `basic.ErrInvalidObservationInput` for invalid input.
|
||||
- Event searches use the ephemeris, which a module method can fix; `StarAltitudeCrossing`, `StarCoordinate*` and `LimitingMagnitude` are independent of the ephemeris.
|
||||
- For differences in convention such as planetary apparent magnitude and right-ascension stations see the [planet manual](planets.md); for Sun and Moon rise and set and observing-angle semantics see [Sun and Moon](sun-moon.md).
|
||||
+105
-84
@@ -6,14 +6,14 @@ The seven major planets each have a package of their own: `mercury`, `venus`, `m
|
||||
|
||||
They share the same API shape: a civil instant is passed as `time.Time`, angles and distances come back as `float64`, and event searches return `time.Time` or a struct.
|
||||
|
||||
The inner planets (Mercury and Venus) add superior/inferior conjunction, greatest elongation, and geocentric transit; the outer planets (Mars through Neptune) add opposition and quadrature; Jupiter alone has the Galilean satellites and Saturn alone has ring parameters. The low-level VSOP87 series and the solar/lunar analytic series live in the `planet` package, shared by these seven packages and by `sun` / `moon`.
|
||||
The inner planets (Mercury and Venus) add superior/inferior conjunction, greatest elongation, and geocentric and topocentric transits; the outer planets (Mars through Neptune) add opposition and quadrature; Jupiter alone has the Galilean satellites and Saturn alone has ring parameters. The `planet` package holds the built-in semianalytic model and the Legacy series, shared by these packages and by `sun` / `moon`. Default builds use to semianalytic; use an explicit Context to fix the model.
|
||||
|
||||
- The function names of the common capabilities are identical across the seven packages (for example all of them provide `ApparentRa`, `ApparentDec`, and `ApparentRaDec`); only the extra families differ.
|
||||
|
||||
Always qualify a call with its package name instead of mixing packages.
|
||||
- Position functions return a **geocentric apparent place**.
|
||||
|
||||
Topocentric quantities and horizontal coordinates are separate: `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle`, with formulas under [Coordinate Tools](coord.md).
|
||||
Horizontal quantities are separate: `Altitude` / `Azimuth` / `Zenith` are topocentric, while `HourAngle` / `ParallacticAngle` are geocentric (no topocentric parallax), with formulas under [Coordinate Tools](coord.md).
|
||||
- Event-search families come as `Last...` / `Next...` pairs (some also `Closest...`) and always return the nearest event at or before/after the input instant, endpoints included.
|
||||
- The planets have no dedicated SVG chart entry point; occultation-related charts (including Saturn-ring occultations) are documented in [Lunar Occultations](occultation.md#lunar-occultation-charts).
|
||||
|
||||
@@ -23,7 +23,7 @@ The inner planets (Mercury and Venus) add superior/inferior conjunction, greates
|
||||
- [API Reference](#api-reference)
|
||||
- [Cross-planet comparison](#cross-planet-comparison)
|
||||
- [Common capabilities and units](#common-capabilities-and-units)
|
||||
- [The `...N` truncation family](#the-n-truncation-family)
|
||||
- [The `...N` family](#the-n-family)
|
||||
- [Usage examples](#usage-examples)
|
||||
- [Which planet can I see tonight?](#which-planet-can-i-see-tonight)
|
||||
- [Oppositions, conjunctions, elongations, stations and retrogrades](#oppositions-conjunctions-elongations-stations-and-retrogrades)
|
||||
@@ -43,7 +43,7 @@ The inner planets (Mercury and Venus) add superior/inferior conjunction, greates
|
||||
- [Division of labour with other manuals](#division-of-labour-with-other-manuals)
|
||||
- [Physical ephemerides](#physical-ephemerides)
|
||||
- [Galilean satellites of Jupiter](#galilean-satellites-of-jupiter)
|
||||
- [Shared types and constants from the `planet` package](#shared-types-and-constants-from-the-planet-package)
|
||||
- [Shared low-level entry points in the `planet` package](#shared-low-level-entry-points-in-the-planet-package)
|
||||
- [Parameter and result conventions](#parameter-and-result-conventions)
|
||||
- [Time scale and civil time](#time-scale-and-civil-time)
|
||||
- [Units and conventions](#units-and-conventions)
|
||||
@@ -86,7 +86,7 @@ func main() {
|
||||
|
||||
Rows list capabilities and columns list planetary packages. Qualify each function with its package, for example `mars.NextOpposition`. A slash separates function names; `—` means the package has no corresponding API.
|
||||
|
||||
| Capability | `mercury` | `venus` | `mars` | `jupiter` | `saturn` | `uranus` | `neptune` |
|
||||
| Quantity | `mercury` | `venus` | `mars` | `jupiter` | `saturn` | `uranus` | `neptune` |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| Apparent ecliptic longitude / latitude | `ApparentLo` / `ApparentBo` | `ApparentLo` / `ApparentBo` | `ApparentLo` / `ApparentBo` | `ApparentLo` / `ApparentBo` | `ApparentLo` / `ApparentBo` | `ApparentLo` / `ApparentBo` | `ApparentLo` / `ApparentBo` |
|
||||
| Apparent right ascension / declination | `ApparentRa` / `ApparentDec` / `ApparentRaDec` | `ApparentRa` / `ApparentDec` / `ApparentRaDec` | `ApparentRa` / `ApparentDec` / `ApparentRaDec` | `ApparentRa` / `ApparentDec` / `ApparentRaDec` | `ApparentRa` / `ApparentDec` / `ApparentRaDec` | `ApparentRa` / `ApparentDec` / `ApparentRaDec` | `ApparentRa` / `ApparentDec` / `ApparentRaDec` |
|
||||
@@ -95,7 +95,7 @@ Rows list capabilities and columns list planetary packages. Qualify each functio
|
||||
| Orbital ascending / descending node | `AscendingNode` / `DescendingNode` | `AscendingNode` / `DescendingNode` | `AscendingNode` / `DescendingNode` | `AscendingNode` / `DescendingNode` | `AscendingNode` / `DescendingNode` | `AscendingNode` / `DescendingNode` | `AscendingNode` / `DescendingNode` |
|
||||
| Apparent diameter / semidiameter | `Diameter` / `Semidiameter` | `Diameter` / `Semidiameter` | `Diameter` / `Semidiameter` | `Diameter` / `Semidiameter` | `Diameter` / `Semidiameter` | `Diameter` / `Semidiameter` | `Diameter` / `Semidiameter` |
|
||||
| Phase angle / illuminated fraction / bright-limb position angle | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` | `PhaseAngle` / `Phase` / `IlluminatedFraction` / `BrightLimbPositionAngle` |
|
||||
| Topocentric horizontal quantities | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` |
|
||||
| Horizontal quantities | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` | `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` |
|
||||
| Rise / set / culmination | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` | `RiseTime` / `SetTime` / `DownTime` / `CulminationTime` |
|
||||
| Physical ephemeris | `Physical` / `PhysicalN` | `Physical` / `PhysicalN` | `Physical` / `PhysicalN` | `Physical` / `PhysicalN` / `CentralMeridians` / `CentralMeridiansN` | `Physical` / `PhysicalN` / `PhysicalSystemIII` / `Ring` | `Physical` / `PhysicalN` / `PhysicalSystemIII` | `Physical` / `PhysicalN` |
|
||||
| Conjunction | `LastConjunction` / `NextConjunction` | `LastConjunction` / `NextConjunction` | `LastConjunction` / `NextConjunction` | `LastConjunction` / `NextConjunction` | `LastConjunction` / `NextConjunction` | `LastConjunction` / `NextConjunction` | `LastConjunction` / `NextConjunction` |
|
||||
@@ -104,25 +104,27 @@ Rows list capabilities and columns list planetary packages. Qualify each functio
|
||||
| Opposition | — | — | `LastOpposition` / `NextOpposition` | `LastOpposition` / `NextOpposition` | `LastOpposition` / `NextOpposition` | `LastOpposition` / `NextOpposition` | `LastOpposition` / `NextOpposition` |
|
||||
| Quadrature | — | — | `LastEasternQuadrature` / `NextEasternQuadrature` / `LastWesternQuadrature` / `NextWesternQuadrature` | `LastEasternQuadrature` / `NextEasternQuadrature` / `LastWesternQuadrature` / `NextWesternQuadrature` | `LastEasternQuadrature` / `NextEasternQuadrature` / `LastWesternQuadrature` / `NextWesternQuadrature` | `LastEasternQuadrature` / `NextEasternQuadrature` / `LastWesternQuadrature` / `NextWesternQuadrature` | `LastEasternQuadrature` / `NextEasternQuadrature` / `LastWesternQuadrature` / `NextWesternQuadrature` |
|
||||
| Greatest elongation | `LastGreatestElongation` / `NextGreatestElongation` / `LastGreatestElongationEast` / `NextGreatestElongationEast` / `LastGreatestElongationWest` / `NextGreatestElongationWest` | `LastGreatestElongation` / `NextGreatestElongation` / `LastGreatestElongationEast` / `NextGreatestElongationEast` / `LastGreatestElongationWest` / `NextGreatestElongationWest` | — | — | — | — | — |
|
||||
| Geocentric transit | `LastTransit` / `NextTransit` / `ClosestTransit` | `LastTransit` / `NextTransit` / `ClosestTransit` | — | — | — | — | — |
|
||||
| Transit | `LastTransit` / `NextTransit` / `ClosestTransit` / `LocalTransit` | `LastTransit` / `NextTransit` / `ClosestTransit` / `LocalTransit` | — | — | — | — | — |
|
||||
| Galilean satellites | — | — | — | `Satellites` / `SatellitePhenomena` / `LastGalileanPhenomenonEvent` / `NextGalileanPhenomenonEvent` / `ClosestGalileanPhenomenonEvent` / `LastGalileanPhenomenonContactEvent` / `NextGalileanPhenomenonContactEvent` / `ClosestGalileanPhenomenonContactEvent` | — | — | — |
|
||||
| Result types | `PhysicalInfo` / `TransitInfo` | `PhysicalInfo` / `TransitInfo` | `PhysicalInfo` | `PhysicalInfo` / `CentralMeridianInfo` / `GalileanSatellitesInfo` / `GalileanPhenomenaInfo` / `GalileanSatellitePosition` / `GalileanSatellitePhenomenon` / `GalileanPhenomenonEvent` / `GalileanPhenomenonContactEvent` | `PhysicalInfo` / `RingInfo` | `PhysicalInfo` | `PhysicalInfo` |
|
||||
| Result types | `PhysicalInfo` / `TransitInfo` / `LocalTransitInfo` | `PhysicalInfo` / `TransitInfo` / `LocalTransitInfo` | `PhysicalInfo` | `PhysicalInfo` / `CentralMeridianInfo` / `GalileanSatellitesInfo` / `GalileanPhenomenaInfo` / `GalileanSatellitePosition` / `GalileanSatellitePhenomenon` / `GalileanPhenomenonEvent` / `GalileanPhenomenonContactEvent` | `PhysicalInfo` / `RingInfo` | `PhysicalInfo` | `PhysicalInfo` |
|
||||
|
||||
All seven packages spell the **truncation family** the same way: append `N` to any instantaneous evaluation function, where `n < 0` uses the full built-in series and `n >= 0` truncates it.
|
||||
All seven packages spell the **`...N` family** the same way: append `N` to an instantaneous evaluation function. `n < 0` selects the full series; all nonnegative `n` select one fixed semianalytic fast table, while Legacy truncates according to `n`.
|
||||
|
||||
See [the `...N` truncation family](#the-n-truncation-family) for details. Event-search families (`Last...` / `Next...` / `Closest...`) have no `N` variants.
|
||||
See [the `...N` family](#the-n-family) for details. Event-search families (`Last...` / `Next...` / `Closest...`) have no `N` variants.
|
||||
|
||||
### Common capabilities and units
|
||||
|
||||
| Capability | Purpose | Unit and convention |
|
||||
| Quantity | Purpose | Unit and convention |
|
||||
| --- | --- | --- |
|
||||
| `ApparentLo` / `ApparentBo` | Geocentric apparent ecliptic longitude and latitude | degrees; true equinox of date, including light time, aberration, and nutation |
|
||||
| `ApparentRa` / `ApparentDec` / `ApparentRaDec` | Geocentric apparent right ascension and declination | degrees; true equator of date; `ApparentRaDec` returns both at once |
|
||||
| `ApparentMagnitude` | Apparent visual magnitude | magnitudes (dimensionless) |
|
||||
| `Altitude` / `Azimuth` / `Zenith` / `HourAngle` | Topocentric horizontal coordinates | degrees; azimuth runs from north toward east, and `Zenith` equals `90 - Altitude` |
|
||||
| `Altitude` / `Azimuth` / `Zenith` | Topocentric horizontal coordinates | degrees; azimuth runs from north toward east, and `Zenith` equals `90 - Altitude` |
|
||||
| `HourAngle` | Geocentric apparent hour angle, without topocentric parallax | degrees |
|
||||
| `RiseTime` / `SetTime` / `DownTime` | Rise and set within the local civil day | `time.Time` keeping the input time zone; `DownTime` is a compatibility alias for `SetTime` |
|
||||
| `RiseTimeWithAtmosphere` / `SetTimeWithAtmosphere` | Rise and set with measured pressure (hPa) and temperature (°C) | `time.Time`; refraction always applied |
|
||||
| `CulminationTime` | Upper culmination instant | `time.Time` keeping the input time zone |
|
||||
| `ParallacticAngle` | Parallactic angle (zenith direction angle) | degrees |
|
||||
| `ParallacticAngle` | Geocentric parallactic angle (zenith direction angle), from the geocentric hour angle and declination | degrees |
|
||||
| `Diameter` / `Semidiameter` | Geocentric apparent diameter and semidiameter | arcseconds |
|
||||
| `PhaseAngle` | Sun-planet-Earth angle | degrees |
|
||||
| `IlluminatedFraction` / `Phase` | Illuminated fraction | `0-1`; `Phase` is an alias for `IlluminatedFraction` |
|
||||
@@ -130,18 +132,23 @@ See [the `...N` truncation family](#the-n-truncation-family) for details. Event-
|
||||
| `EarthDistance` / `SunDistance` | Earth distance and Sun distance | AU |
|
||||
| `AscendingNode` / `DescendingNode` | Ecliptic longitude of the orbital plane's intersections with the ecliptic | degrees; the two differ by about `180°` at the same instant |
|
||||
| `Physical` | Disk orientation, sub-Earth/sub-Solar coordinates, north-pole position angle | degrees; the positive longitude direction follows each body's IAU convention |
|
||||
| `planet.WherePlanet` / `planet.WherePlanetN` | VSOP87 longitude, latitude, and heliocentric distance | degrees / AU; out-of-range input returns `NaN` instead of panicking |
|
||||
| `planet.WherePlanet` / `planet.WherePlanetN` | Selected built-in model's longitude, latitude, and heliocentric distance | degrees / AU; out-of-range input returns `NaN` instead of panicking |
|
||||
|
||||
### The `...N` truncation family
|
||||
### The `...N` family
|
||||
|
||||
Every instantaneous evaluation function has an `...N` variant that trades accuracy for cost: `n < 0` uses the full built-in series and is equivalent to the plain function, while `n >= 0` keeps roughly `n` principal terms and scales the higher orders proportionally.
|
||||
Every instantaneous evaluation function has an `...N` variant that trades accuracy for cost. `n < 0` selects the full series and matches the plain function. All nonnegative `n` select one fixed semianalytic fast table, so increasing `n` does not improve accuracy; Legacy scales its principal and higher-order terms according to `n`.
|
||||
|
||||
Event-search families (`Last...` / `Next...` / `Closest...`) have no `N` variant.
|
||||
|
||||
Functions with an `N` variant include `ApparentLoN`, `ApparentBoN`, `ApparentRaN`, `ApparentDecN`, `ApparentRaDecN`, `ApparentMagnitudeN`, `EarthDistanceN`, `SunDistanceN`, `AltitudeN`, `AzimuthN`, `ZenithN`, `HourAngleN`, `CulminationTimeN`, `RiseTimeN`, `SetTimeN`, `DownTimeN`, `ParallacticAngleN`, `DiameterN`, `SemidiameterN`, `PhaseAngleN`, `PhaseN`, `IlluminatedFractionN`, `BrightLimbPositionAngleN`, `AscendingNodeN`, `DescendingNodeN`, and `PhysicalN`, plus `CentralMeridiansN` for Jupiter, `RingN` and `PhysicalSystemIIIN` for Saturn, and `PhysicalSystemIIIN` for Uranus.
|
||||
Functions with an `N` variant, grouped by purpose:
|
||||
|
||||
- Position and distance: `ApparentLoN`, `ApparentBoN`, `ApparentRaN`, `ApparentDecN`, `ApparentRaDecN`, `EarthDistanceN`, `SunDistanceN`
|
||||
- Horizon and instants: `AltitudeN`, `AzimuthN`, `ZenithN`, `HourAngleN`, `CulminationTimeN`, `RiseTimeN`, `SetTimeN`, `DownTimeN`, `ParallacticAngleN`
|
||||
- Disc and phase: `ApparentMagnitudeN`, `DiameterN`, `SemidiameterN`, `PhaseAngleN`, `PhaseN`, `IlluminatedFractionN`, `BrightLimbPositionAngleN`
|
||||
- Nodes and rotation: `AscendingNodeN`, `DescendingNodeN`, `PhysicalN`, plus `CentralMeridiansN` for Jupiter, `RingN` and `PhysicalSystemIIIN` for Saturn, and `PhysicalSystemIIIN` for Uranus
|
||||
|
||||
```go
|
||||
fmt.Println(mars.ApparentLo(date), mars.ApparentLoN(date, 8)) // ecliptic longitude: full series / truncated to about 8 terms
|
||||
fmt.Println(mars.ApparentLo(date), mars.ApparentLoN(date, 8)) // full series / fast table; Legacy truncates according to n
|
||||
fmt.Println(mars.SunDistance(date), mars.SunDistanceN(date, 8)) // heliocentric distance, AU
|
||||
```
|
||||
|
||||
@@ -161,7 +168,7 @@ fmt.Println(mars.Altitude(date, lon, lat), mars.Azimuth(date, lon, lat)) // Mars
|
||||
31.194578177219057 152.07031660415714
|
||||
```
|
||||
|
||||
Above the horizon means `Altitude` greater than `0`, and azimuth increases from due north towards the east; `aero = true` lowers the geometric horizon to about `-0.5667°` for standard refraction.
|
||||
Azimuth increases from due north towards the east; `aero = true` lowers the geometric horizon to about `-0.5667°` for standard refraction.
|
||||
|
||||
Per-parameter conventions and the polar sentinel errors are in [Rise, set and culmination](#rise-set-and-culmination).
|
||||
|
||||
@@ -196,7 +203,21 @@ true 2032-11-13 14:41:13.161198198 +0800 CST 2032-11-13 16:54:12.821315824 +0800
|
||||
4h26m2.695272267s 572.0643215495325
|
||||
```
|
||||
|
||||
`TransitInfo.Valid` false means no transit inside the search window and every other field is the zero value; a transit is a geocentric geometry test and does not check whether the Sun is above the horizon at a given site.
|
||||
`TransitInfo.Valid` false means no transit inside the search window and every other field is the zero value; `TransitInfo` is the geocentric geometry. For the contact times seen from one site, `LocalTransit` returns the topocentric result:
|
||||
|
||||
```go
|
||||
transit := mercury.NextTransit(time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||
local := mercury.LocalTransit(transit, -77.0, 38.9, 0) // Washington: longitude, latitude, height in metres
|
||||
fmt.Println(local.Valid, local.HasInternal, local.Visible) // topocentric transit, internal contacts, Sun above the apparent horizon
|
||||
fmt.Println(local.Start.Time, local.Start.SunAltitude) // topocentric C1 and the Sun's altitude then
|
||||
```
|
||||
|
||||
```text
|
||||
true true true
|
||||
2019-11-11 12:36:09.037762284 +0000 UTC 7.933256722170642
|
||||
```
|
||||
|
||||
Topocentric contacts are re-solved after applying parallax to the Sun and the planet separately; Venus contacts can shift by about ±7 minutes from the geocentric ones across the globe, Mercury by about ±2 minutes. `SunAltitude` is the geometric altitude of the Sun's center; `Visible`, `Sunrise` and `Sunset` test whether the Sun's center is above the apparent horizon with standard refraction (1010 hPa, 10 °C), a geometric threshold of about −34′. A site may see only a grazing transit even when the geocentric one has internal contacts (the 1999-11-15 Mercury transit at high southern latitudes); then `HasInternal` is false and `InternalStart` / `InternalEnd` are zero. Global maps and topocentric disk charts are in [Eclipses and transits](eclipse.md#transit-charts).
|
||||
|
||||
The four contacts, partial transits and the internal-contact fields are in [Greatest elongation and geocentric transits](#greatest-elongation-and-geocentric-transits).
|
||||
|
||||
@@ -216,7 +237,7 @@ fmt.Println(mars.AscendingNode(date), mars.DescendingNode(date)) // ascending/de
|
||||
|
||||
`Phase` is an alias of `IlluminatedFraction` and runs from `0` to `1`; the two nodes are about `180°` apart at any instant.
|
||||
|
||||
Definitions, aliases and the truncated versions are in [Nodes, phase, magnitude, diameter and parallactic angle](#nodes-phase-magnitude-diameter-and-parallactic-angle).
|
||||
Definitions, aliases and the `n` variants are in [Nodes, phase, magnitude, diameter and parallactic angle](#nodes-phase-magnitude-diameter-and-parallactic-angle).
|
||||
|
||||
### Physical ephemerides and the Galilean satellites
|
||||
|
||||
@@ -237,7 +258,7 @@ Saturn's rings have their own `saturn.Ring` (`EarthLatitude`, `MinorAxis`, and s
|
||||
### Comparison conventions
|
||||
|
||||
```go
|
||||
fmt.Println(mars.ApparentLo(date), mars.ApparentLoN(date, 8)) // apparent longitude with every term / truncated to about 8
|
||||
fmt.Println(mars.ApparentLo(date), mars.ApparentLoN(date, 8)) // full series / fast table; Legacy truncates according to n
|
||||
_, err := mars.RiseTime(date, 0, 89, 0, true) // an observing site in the polar region
|
||||
fmt.Println(errors.Is(err, mars.ERR_MARS_NEVER_RISE), errors.Is(err, mars.ERR_MARS_NEVER_SET))
|
||||
```
|
||||
@@ -247,9 +268,9 @@ fmt.Println(errors.Is(err, mars.ERR_MARS_NEVER_RISE), errors.Is(err, mars.ERR_MA
|
||||
true false
|
||||
```
|
||||
|
||||
`ApparentLo` and friends are geocentric apparent places referred to the **true equinox of date**; bring external J2000 or mean places to the same convention with `coord.Precess` before comparing.
|
||||
`ApparentLo` and friends are geocentric apparent places referred to the **true equinox of date**. Comparing them with external J2000 or mean places requires matching the frame, nutation and apparent-place corrections; precession alone does not remove every difference.
|
||||
|
||||
In the truncation family `n < 0` uses every built-in term and `n >= 0` truncates.
|
||||
For model-dependent `n` semantics, see [the `...N` family](#the-n-family).
|
||||
|
||||
The convention gap between the Galilean contact events and JPL Horizons / IMCCE tables is documented under [External baselines](#external-baselines), and the overall boundaries under [Parameter and result conventions](#parameter-and-result-conventions).
|
||||
|
||||
@@ -308,20 +329,20 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
2019-11-11 23:21:41.971051096 +0800 CST // previous inferior conjunction of Mercury
|
||||
2021-03-26 14:57:42.052354216 +0800 CST // next superior conjunction of Venus
|
||||
2019-11-01 04:31:49.749019145 +0800 CST // previous Mercury station from prograde to retrograde
|
||||
2020-06-25 02:07:41.599749326 +0800 CST // next Venus station from retrograde to prograde
|
||||
2019-10-20 12:01:37.740152478 +0800 CST // previous greatest eastern elongation of Mercury
|
||||
2020-08-13 08:14:46.304587125 +0800 CST // next greatest western elongation of Venus
|
||||
2020-01-01 10:02:34.172435402 +0800 CST <nil> // Venus rise time in Xi'an; no error
|
||||
2020-01-01 20:25:37.36411482 +0800 CST <nil> // Venus set time in Xi'an; no error
|
||||
2019-11-11 23:21:41.729008555 +0800 CST // previous inferior conjunction of Mercury
|
||||
2021-03-26 14:57:43.352850079 +0800 CST // next superior conjunction of Venus
|
||||
2019-11-01 04:31:49.712648391 +0800 CST // previous Mercury station from prograde to retrograde
|
||||
2020-06-25 02:07:26.280500292 +0800 CST // next Venus station from retrograde to prograde
|
||||
2019-10-20 12:01:37.925546765 +0800 CST // previous greatest eastern elongation of Mercury
|
||||
2020-08-13 08:14:45.946914553 +0800 CST // next greatest western elongation of Venus
|
||||
2020-01-01 10:02:34.94517982 +0800 CST <nil> // Venus rise time in Xi'an; no error
|
||||
2020-01-01 20:25:36.944952607 +0800 CST <nil> // Venus set time in Xi'an; no error
|
||||
-4 // Venus apparent magnitude
|
||||
49.98145049145023 // Venus phase angle, degrees
|
||||
0.8215177914415865 // illuminated fraction of Venus
|
||||
255.63802053541346 // bright-limb position angle of Venus, degrees
|
||||
1.2778819631550336 // Earth-Venus distance, AU
|
||||
0.7262651056423838 // Sun-Venus distance, AU
|
||||
49.98142313573699 // Venus phase angle, degrees
|
||||
0.8215179742647736 // illuminated fraction of Venus
|
||||
255.63803631896667 // bright-limb position angle of Venus, degrees
|
||||
1.2778823747747166 // Earth-Venus distance, AU
|
||||
0.7262651318993368 // Sun-Venus distance, AU
|
||||
```
|
||||
|
||||
Inner and outer planets also expose `Diameter` / `Semidiameter` and `N` variants, returning geocentric apparent diameter/semidiameter in arcseconds.
|
||||
@@ -346,7 +367,7 @@ For `date := 2020-01-01 08:08:08 CST`, the output is:
|
||||
76.86008484515058 256.8600848451506 // Venus ascending-node and descending-node longitudes, degrees
|
||||
```
|
||||
|
||||
Mercury and Venus also expose `NextTransit` / `LastTransit` / `ClosestTransit` for geocentric planetary transits. "Geocentric" means the planet disk crosses the solar disk as seen from Earth's center; it does not test whether the Sun is above the horizon at a particular observing site. For observing plans, combine this with local solar altitude and weather.
|
||||
Mercury and Venus also expose `NextTransit` / `LastTransit` / `ClosestTransit` for geocentric planetary transits. "Geocentric" means the planet disk crosses the solar disk as seen from Earth's center; it does not test whether the Sun is above the horizon at a particular observing site. For observing plans, use `LocalTransit` for topocentric contacts and solar altitudes (see above).
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -389,22 +410,22 @@ Output:
|
||||
|
||||
```text
|
||||
true // a valid geocentric Mercury transit was found
|
||||
2019-11-11 12:35:31.567597389 +0000 UTC // first contact: Mercury externally enters the solar disk
|
||||
2019-11-11 12:37:12.817581295 +0000 UTC // second contact: Mercury is fully inside the solar disk
|
||||
2019-11-11 15:19:48.36056292 +0000 UTC // greatest transit: Mercury center is closest to the Sun center
|
||||
2019-11-11 18:02:29.176982045 +0000 UTC // third contact: Mercury starts leaving the solar disk
|
||||
2019-11-11 18:04:10.637948513 +0000 UTC // fourth contact: Mercury externally leaves the solar disk
|
||||
5h28m39.070351124s // geocentric transit duration from first to fourth contact
|
||||
75.92400059923187 // minimum Mercury-Sun center separation at greatest transit, arcseconds
|
||||
968.8881519533047 // solar semidiameter at greatest transit, arcseconds
|
||||
4.978442871670873 // Mercury semidiameter at greatest transit, arcseconds
|
||||
2019-11-11 12:35:31.142292916 +0000 UTC // first contact: Mercury externally enters the solar disk
|
||||
2019-11-11 12:37:12.603219151 +0000 UTC // second contact: Mercury is fully inside the solar disk
|
||||
2019-11-11 15:19:48.146160542 +0000 UTC // greatest transit: Mercury center is closest to the Sun center
|
||||
2019-11-11 18:02:28.751637339 +0000 UTC // third contact: Mercury starts leaving the solar disk
|
||||
2019-11-11 18:04:10.212523341 +0000 UTC // fourth contact: Mercury externally leaves the solar disk
|
||||
5h28m39.070230425s // geocentric transit duration from first to fourth contact
|
||||
75.93592092696468 // minimum Mercury-Sun center separation at greatest transit, arcseconds
|
||||
968.8881399120452 // solar semidiameter at greatest transit, arcseconds
|
||||
4.978442587642365 // Mercury semidiameter at greatest transit, arcseconds
|
||||
true // a valid geocentric Venus transit was found
|
||||
2012-06-05 22:09:47.466886639 +0000 UTC // first contact: Venus externally enters the solar disk
|
||||
2012-06-05 22:27:35.865356326 +0000 UTC // second contact: Venus is fully inside the solar disk
|
||||
2012-06-06 01:29:35.572371482 +0000 UTC // greatest transit: Venus center is closest to the Sun center
|
||||
2012-06-06 04:31:35.068444311 +0000 UTC // third contact: Venus starts leaving the solar disk
|
||||
2012-06-06 04:49:23.25597167 +0000 UTC // fourth contact: Venus externally leaves the solar disk
|
||||
6h39m35.789085031s // geocentric transit duration from first to fourth contact
|
||||
2012-06-05 22:09:48.690456748 +0000 UTC // first contact: Venus externally enters the solar disk
|
||||
2012-06-05 22:27:37.088886201 +0000 UTC // second contact: Venus is fully inside the solar disk
|
||||
2012-06-06 01:29:36.58495903 +0000 UTC // greatest transit: Venus center is closest to the Sun center
|
||||
2012-06-06 04:31:35.87008953 +0000 UTC // third contact: Venus starts leaving the solar disk
|
||||
2012-06-06 04:49:24.268559217 +0000 UTC // fourth contact: Venus externally leaves the solar disk
|
||||
6h39m35.578102469s // geocentric transit duration from first to fourth contact
|
||||
```
|
||||
|
||||
### Outer planets
|
||||
@@ -467,18 +488,18 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
2020-10-14 07:25:50.441412627 +0800 CST // next opposition of Mars
|
||||
2021-01-29 09:39:33.697994649 +0800 CST // next conjunction of Jupiter
|
||||
2019-04-30 10:28:00.187439918 +0800 CST // previous Saturn station from prograde to retrograde
|
||||
saturn B=23.577025 Bp=23.266930 P=6.629811 dU=1.171016 major=34.133852 minor=13.652911 // Saturn ring B, B', P, dU, major axis, minor axis
|
||||
2020-01-11 15:23:23.360308706 +0800 CST // next Uranus station from retrograde to prograde
|
||||
2019-12-08 17:00:15.517960488 +0800 CST // previous eastern quadrature of Neptune
|
||||
2020-06-07 03:11:00.026179254 +0800 CST // next western quadrature of Mars
|
||||
2020-01-01 04:41:29.621566236 +0800 CST <nil> // Mars rise time in Xi'an; no error
|
||||
2020-01-01 14:55:32.963508367 +0800 CST <nil> // Mars set time in Xi'an; no error
|
||||
2020-10-14 07:25:53.541214764 +0800 CST // next opposition of Mars
|
||||
2021-01-29 09:39:32.556620836 +0800 CST // next conjunction of Jupiter
|
||||
2019-04-30 10:28:17.254818677 +0800 CST // previous Saturn station from prograde to retrograde
|
||||
saturn B=23.577010 Bp=23.266930 P=6.629810 dU=1.171003 major=34.133617 minor=13.652809 // Saturn ring B, B', P, dU, major axis, minor axis
|
||||
2020-01-11 15:22:18.532456755 +0800 CST // next Uranus station from retrograde to prograde
|
||||
2019-12-08 17:00:04.494322836 +0800 CST // previous eastern quadrature of Neptune
|
||||
2020-06-07 03:10:55.059157311 +0800 CST // next western quadrature of Mars
|
||||
2020-01-01 04:41:30.157055854 +0800 CST <nil> // Mars rise time in Xi'an; no error
|
||||
2020-01-01 14:55:32.792302966 +0800 CST <nil> // Mars set time in Xi'an; no error
|
||||
1.57 // Mars apparent magnitude
|
||||
2.1844284956325937 // Earth-Mars distance, AU
|
||||
1.5897860004265403 // Sun-Mars distance, AU
|
||||
2.1844226019239925 // Earth-Mars distance, AU
|
||||
1.5897858533008575 // Sun-Mars distance, AU
|
||||
```
|
||||
|
||||
`saturn.Ring` returns `RingInfo`: `EarthLatitude` is ring opening angle B, `SunLatitude` is B', `PositionAngle` is the position angle of the northern semiminor axis, `DeltaU` is the Saturnicentric longitude difference between the Sun and Earth in the ring plane, and `MajorAxis` / `MinorAxis` are the apparent outer major/minor axes in arcseconds.
|
||||
@@ -505,9 +526,9 @@ fmt.Println(venus.EarthDistance(date), venus.SunDistance(date))
|
||||
|
||||
### Rise, set and culmination
|
||||
|
||||
`RiseTime` / `SetTime` / `DownTime` work on the **local civil day** only: `date` selects the local date and the output time zone (when the local hour is greater than 12 it is first shifted back by 12 hours), `height` is the ellipsoidal height in meters, and `aero == true` adds standard atmospheric refraction (lowering the geometric horizon to about `-0.5667°`).
|
||||
`RiseTime` / `SetTime` / `DownTime` work on the **local civil day** only: `date` selects the local date and the output time zone, and the returned events belong to that local civil day; `height` is the ellipsoidal height in meters, and `aero == true` adds standard atmospheric refraction (lowering the geometric horizon to about `-0.5667°`).
|
||||
|
||||
Polar day, polar night, or a day with no crossing returns a [sentinel error](#parameter-and-result-conventions) instead of an instant. `CulminationTime` gives upper culmination; `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` form the topocentric family, with geometry under [Coordinate Tools](coord.md).
|
||||
Polar day, polar night, or a day with no crossing returns a [sentinel error](#parameter-and-result-conventions) instead of an instant. `CulminationTime` gives upper culmination; `Altitude` / `Azimuth` / `Zenith` are topocentric instantaneous quantities and `HourAngle` / `ParallacticAngle` geocentric ones, with geometry under [Coordinate Tools](coord.md).
|
||||
|
||||
```go
|
||||
// Rise, set, and culmination on the local civil day in Xi'an; aero=true adds refraction.
|
||||
@@ -525,7 +546,7 @@ fmt.Println(mars.ParallacticAngle(date, lon, lat))
|
||||
```
|
||||
|
||||
```go
|
||||
// DownTime is a compatibility alias for SetTime; the N variant evaluates a truncated series.
|
||||
// DownTime is a compatibility alias for SetTime; the N variant evaluates the `n` variant.
|
||||
set, err := mars.DownTime(date, lon, lat, height, true)
|
||||
fmt.Println(set, err)
|
||||
fmt.Println(mars.CulminationTimeN(date, lon, 12))
|
||||
@@ -597,7 +618,7 @@ fmt.Println(transit.SunSemidiameterArcsec, transit.PlanetSemidiameterArcsec)
|
||||
|
||||
`PhaseAngle` is the Sun-planet-Earth angle in degrees; `IlluminatedFraction` (alias `Phase`) is the illuminated fraction from `0` to `1`; `BrightLimbPositionAngle` is the position angle of the bright-limb center in degrees.
|
||||
|
||||
`Diameter` / `Semidiameter` return the geocentric apparent diameter and semidiameter in arcseconds. `ParallacticAngle` returns the topocentric parallactic angle (zenith direction angle) in degrees; topocentric coordinates are under [Coordinate Tools](coord.md).
|
||||
`Diameter` / `Semidiameter` return the geocentric apparent diameter and semidiameter in arcseconds. `ParallacticAngle` returns the geocentric parallactic angle (zenith direction angle) in degrees, computed from the geocentric hour angle and declination without topocentric parallax; topocentric coordinates are under [Coordinate Tools](coord.md).
|
||||
|
||||
```go
|
||||
fmt.Println(mars.AscendingNode(date), mars.DescendingNode(date)) // node longitudes, degrees
|
||||
@@ -611,7 +632,7 @@ fmt.Println(mars.ParallacticAngle(date, lon, lat)) // parallactic angle,
|
||||
```
|
||||
|
||||
```go
|
||||
// Nodes and apparent diameter also have truncated variants.
|
||||
// Nodes and apparent diameter also have `n` variants.
|
||||
fmt.Println(mars.AscendingNodeN(date, 12), mars.DescendingNodeN(date, 12))
|
||||
fmt.Println(mars.DiameterN(date, 12), mars.SemidiameterN(date, 12))
|
||||
```
|
||||
@@ -669,8 +690,8 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
jupiter DS=54.342153 DE=1.436485 CMI=292.712909 CMII=276.309048 CMIII=147.241811 // Jupiter DS/DE and System I/II/III central meridians, degrees
|
||||
saturn B=-0.608048 Bp=-2.675677 P=4.480276 major=42.709920 minor=0.453248 // Saturn ring B, B', minor-axis position angle, outer major/minor axes
|
||||
jupiter DS=54.342201 DE=1.436449 CMI=292.712946 CMII=276.309085 CMIII=147.241848 // Jupiter DS/DE and System I/II/III central meridians, degrees
|
||||
saturn B=-0.608032 Bp=-2.675667 P=4.480277 major=42.709152 minor=0.453228 // Saturn ring B, B', minor-axis position angle, outer major/minor axes
|
||||
```
|
||||
|
||||
If only Jupiter central meridians are needed:
|
||||
@@ -796,8 +817,8 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
io x=-0.675026 y=-0.032798 front=true // Io X/Y offset from Jupiter center, in Jupiter radii; in front of Jupiter
|
||||
europa ra=110.769133 dec=22.335828 // Europa apparent RA and Dec, degrees
|
||||
io x=-0.675023 y=-0.032794 front=true // Io X/Y offset from Jupiter center, in Jupiter radii; in front of Jupiter
|
||||
europa ra=110.769095 dec=22.335877 // Europa apparent RA and Dec, degrees
|
||||
io transit=true occultation=false eclipse=false shadow=true // Io is transiting, and its shadow is also transiting
|
||||
europa transit=false occultation=false eclipse=false shadow=false // Europa has no transit, occultation, eclipse, or shadow transit at this instant
|
||||
event valid=true sat=1 type=transit // next valid event is an Io transit
|
||||
@@ -829,15 +850,15 @@ Comparison results:
|
||||
- `GalileanPhenomenonContactEvent` differs from IMCCE 2026 D/F contact times by at most about `79 s`, and contact durations by about `17 s` in the compared cases.
|
||||
- `GalileanPhenomenonEvent` uses a different definition from IMCCE D/F contacts, so start and end times can differ by about `7 min`.
|
||||
|
||||
### Shared types and constants from the `planet` package
|
||||
### Shared low-level entry points in the `planet` package
|
||||
|
||||
The `planet` package is the low-level analytic-series entry point, shared by the seven planet packages and by `sun` / `moon`.
|
||||
The `planet` package provides low-level entry points to the built-in models and series, shared by the seven planet packages and by `sun` / `moon`.
|
||||
|
||||
It exports functions only and **no types**, so what the planet packages really share is one set of numerical conventions and truncation semantics rather than shared types: each package's `PhysicalInfo` mirrors `basic.PlanetPhysicalInfo` and `TransitInfo` mirrors `basic.PlanetTransitResult`, field for field, while the types themselves still belong to their own packages.
|
||||
It exports functions only, not types. The planet packages share numerical conventions and model choices rather than result types: each package's `PhysicalInfo` mirrors `basic.PlanetPhysicalInfo` and `TransitInfo` mirrors `basic.PlanetTransitResult`, field for field, while the types themselves still belong to their own packages. External kernel-engine types live in the separate `planet/spk` subpackage.
|
||||
|
||||
| Entry point | Purpose | Unit |
|
||||
| --- | --- | --- |
|
||||
| `planet.WherePlanet` / `planet.WherePlanetN` | VSOP87 result: `xt` is `1..7` for Mercury through Neptune and `-1` or `0` for Earth, `zn` is `0` longitude, `1` latitude, `2` heliocentric distance; an out-of-range `xt` / `zn` returns `NaN` instead of panicking | degrees / AU |
|
||||
| `planet.WherePlanet` / `planet.WherePlanetN` | Selected built-in model: `xt` is `1..7` for Mercury through Neptune and `-1` or `0` for Earth, `zn` is `0` longitude, `1` latitude, `2` heliocentric distance; an out-of-range `xt` / `zn` returns `NaN` instead of panicking | degrees / AU |
|
||||
| `planet.Distance` | Sun-Earth distance | AU |
|
||||
| `planet.SunLo` / `planet.SunM` / `planet.SunMidFun` / `planet.SunTrueLo` / `planet.SunApparentLo` | Solar geometric longitude, mean anomaly, equation of center, true longitude, apparent longitude | degrees |
|
||||
| `planet.Earthe` / `planet.EarthPI` | Earth's orbital eccentricity and longitude of perihelion | dimensionless / degrees |
|
||||
@@ -848,7 +869,7 @@ It exports functions only and **no types**, so what the planet packages really s
|
||||
```go
|
||||
// xt=1..7 is Mercury..Neptune; zn=0 longitude, 1 latitude, 2 heliocentric distance (AU).
|
||||
fmt.Println(planet.WherePlanet(4, 2, 2460000.5)) // Jupiter heliocentric distance, AU
|
||||
fmt.Println(planet.WherePlanetN(4, 2, 2460000.5, 12)) // truncated form, about 12 principal terms
|
||||
fmt.Println(planet.WherePlanetN(4, 2, 2460000.5, 12)) // semianalytic fast table; Legacy truncates according to n
|
||||
fmt.Println(planet.WherePlanet(8, 0, 2460000.5)) // xt out of range: NaN
|
||||
```
|
||||
|
||||
@@ -873,17 +894,17 @@ From 1972 on the built-in leap-second table is used, and beyond the exact window
|
||||
|
||||
Angles are always in degrees; apparent diameter and semidiameter are in arcseconds; distances follow the function name -- `EarthDistance` / `SunDistance` and `planet.WherePlanet` with `zn=2` are in AU, while `planet.MoonAway` is in km.
|
||||
|
||||
`PhaseAngle` is in degrees, `IlluminatedFraction` and its alias `Phase` are `0-1`, and `ApparentMagnitude` is a magnitude (dimensionless); rise/set, culmination, and every event search return `time.Time` in the input time zone.
|
||||
`PhaseAngle` is in degrees, `IlluminatedFraction` and its alias `Phase` are `0-1`, and `ApparentMagnitude` is a magnitude (dimensionless, classic phase polynomials); `ApparentMagnitude2018` uses Mallama & Hilton 2018, better for Mercury/Venus at large phase and Saturn's rings, without the Mars rotation and season terms; rise/set, culmination, and every event search return `time.Time` in the input time zone.
|
||||
|
||||
Duration fields inside event structs (`TransitInfo.Duration`, `TransitInfo.InternalDuration`, `GalileanPhenomenonEvent.Duration`, `GalileanPhenomenonContact.Duration`) are Go `time.Duration` values, not Julian days or day counts; the `Start` / `Greatest` / `End` / `InternalStart` / `InternalEnd` fields of `TransitInfo` keep the caller's time zone.
|
||||
|
||||
Geocentric, topocentric, and distance are three different conventions: `ApparentLo` / `ApparentBo` / `ApparentRa` / `ApparentDec` / `ApparentRaDec` are geocentric apparent places, `Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` are topocentric quantities, and `EarthDistance` / `SunDistance` are geometric geocentric distances.
|
||||
Geocentric, topocentric, and distance are three different conventions: `ApparentLo` / `ApparentBo` / `ApparentRa` / `ApparentDec` / `ApparentRaDec` are geocentric apparent places, `Altitude` / `Azimuth` / `Zenith` are topocentric quantities, `HourAngle` / `ParallacticAngle` are geocentric quantities without topocentric parallax, and `EarthDistance` / `SunDistance` are geometric geocentric distances.
|
||||
|
||||
### Zero values, out-of-range input and sentinel errors
|
||||
|
||||
When an event search finds nothing inside its window it returns zero values or a struct with `Valid == false` (`TransitInfo.Valid`, `GalileanPhenomenonEvent.Valid`, `GalileanPhenomenonContactEvent.Valid`) instead of an error; when `TransitInfo.HasInternal` is false, `InternalStart` / `InternalEnd` are zero values because a partial transit has no internal contacts.
|
||||
|
||||
`planet.WherePlanet` / `planet.WherePlanetN` return `NaN` instead of panicking when `xt` / `zn` is out of range; in the `...N` truncation family, `n < 0` uses the full built-in series and `n >= 0` truncates it.
|
||||
`planet.WherePlanet` / `planet.WherePlanetN` return `NaN` instead of panicking when `xt` / `zn` is out of range. `n < 0` selects the full series; all nonnegative `n` select one semianalytic fast table, while Legacy truncates according to `n`.
|
||||
|
||||
`RiseTime` / `SetTime` / `DownTime` (including their `N` forms) are the only family that returns an `error`: when the body has no geometric rise or set on that day they return the sentinel error below with a zero instant.
|
||||
|
||||
@@ -916,12 +937,12 @@ case errors.Is(err, mercury.ERR_MERCURY_NEVER_SET):
|
||||
|
||||
`Altitude` / `Azimuth` / `Zenith` / `HourAngle` / `ParallacticAngle` use `date`'s time zone for local-time computation, with east-positive longitude, north-positive latitude, and `height` as the ellipsoidal height in meters (not orthometric elevation).
|
||||
|
||||
They are a different convention from the geocentric apparent place of `ApparentRa` / `ApparentDec`; convert through [Coordinate Tools](coord.md) before mixing them.
|
||||
`Altitude` / `Azimuth` / `Zenith` include topocentric parallax and are a different convention from the geocentric apparent place of `ApparentRa` / `ApparentDec`; `HourAngle` / `ParallacticAngle` come straight from the geocentric apparent place without topocentric parallax. Convert through [Coordinate Tools](coord.md) before mixing them.
|
||||
|
||||
### Accuracy and scope
|
||||
|
||||
The planet packages use the built-in truncated VSOP87 series, covering about 4000 years around J2000; the truncation magnitudes relative to full VSOP87 are in [Sun and planets](accuracy.md#sun-and-planets) and the overall scope is in [Scope And Accuracy](accuracy.md).
|
||||
The planet packages default to the semianalytic model, with Legacy or custom engines selectable explicitly. An available range does not imply uniform accuracy across it; Legacy truncation error against complete VSOP87 is not total error against DE ephemerides. See [Legacy truncation error](accuracy.md#legacy-truncation-error).
|
||||
|
||||
The `...N` forms relax that baseline further and suit batch scans or live front-end refreshes.
|
||||
The fast table suits coarse scans. Use the full series and matching time/frame conventions for event refinement or comparisons with external tables.
|
||||
|
||||
`saturn.Ring` and the physical ephemerides affect only the disk and band rendering: the Saturn ring takes no part in occultation contact geometry and is not drawn as a disk boundary. The planets have no dedicated chart entry point; occultation and Saturn-ring charts are in [Lunar Occultations](occultation.md#lunar-occultation-charts).
|
||||
|
||||
+59
-9
@@ -8,6 +8,7 @@ The library ships a catalog of 9100 stars (BSC / HR numbers `1-9110`, apparent m
|
||||
|
||||
## Contents
|
||||
|
||||
- [External catalogs](#external-catalogs)
|
||||
- [Finding Sirius and its rise time](#finding-sirius-and-its-rise-time)
|
||||
- [API Reference](#api-reference)
|
||||
- [Constellation lookup](#constellation-lookup)
|
||||
@@ -26,6 +27,55 @@ The library ships a catalog of 9100 stars (BSC / HR numbers `1-9110`, apparent m
|
||||
- [Parameter and result conventions](#parameter-and-result-conventions)
|
||||
- [Related manuals](#related-manuals)
|
||||
|
||||
## External catalogs
|
||||
|
||||
`star/catalog` is independent of the embedded 9100-star database and does not automatically download or load large catalogs. `Entry.Coordinate` is a `basic.StarCoordinate` with epoch, frame and proper motion; pass it directly to `moon.FindStarOccultations` or `phenomena.StarApproaches`.
|
||||
|
||||
| Entry point | Input and purpose |
|
||||
| --- | --- |
|
||||
| `ReadCSV` | Header-based CSV / TSV; `ColumnMap` sets columns, epoch and frame; zero `Delimiter` enables detection |
|
||||
| `ReadHipparcos2007` / `ReadBSC5` | Corresponding fixed-width catalog text |
|
||||
| `ReadStellariumStars` / `ReadStellariumDir` | One binary shard or an `fs.FS` directory; directory options load names/cross-IDs and filter by `MaxMagnitude` |
|
||||
| `Lookup` / `Entries` / `Len` | ID or alias lookup, copied records and count |
|
||||
| `Near` / `Band` | Cone / declination-band searches at catalog epochs, without propagation to the observing date |
|
||||
| `Merge` / `WithNames` | Return new catalogs; right-hand records win for the same primary ID and component; names use `id\|name` lines |
|
||||
|
||||
```go
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"b612.me/astro/basic"
|
||||
"b612.me/astro/star/catalog"
|
||||
)
|
||||
|
||||
input := strings.NewReader("id,ra,dec,pmra,pmdec,V\nSirius,101.28715533,-16.71611586,-546.01,-1223.07,-1.46\n")
|
||||
stars, err := catalog.ReadCSV(input, catalog.ColumnMap{
|
||||
ID: "id", RA: "ra", Dec: "dec", PMRA: "pmra", PMDec: "pmdec", Magnitude: "V",
|
||||
PMRAIncludesCosDec: true, Band: catalog.BandV,
|
||||
Epoch: time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC), Frame: basic.CoordinateFrameJ2000,
|
||||
}, catalog.ReadOptions{Strict: true})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
entry, found := stars.Lookup("Sirius")
|
||||
_ = entry.Coordinate
|
||||
_ = found
|
||||
```
|
||||
|
||||
Units and required fields:
|
||||
|
||||
- `RA` and `Dec` default to degrees; `AnglesInRadians` selects radians. Proper motions are mas/year, parallax mas and radial velocity km/s.
|
||||
- Set `PMRAIncludesCosDec: true` if PMRA already represents `dRA*cos(Dec)`; otherwise the reader multiplies by the cosine.
|
||||
- Supply `Epoch` or `EpochColumn` (Julian year) and the correct `Frame`; a zero epoch is not an unknown-epoch placeholder.
|
||||
|
||||
Reading behavior:
|
||||
|
||||
- With `ReadOptions.Strict`, invalid records return an error; otherwise they are skipped and counted by `Skipped()`. Structural errors such as missing required headers always fail.
|
||||
- `Filter` controls admission without counting rejected records as bad.
|
||||
- Missing magnitudes are `NaN`; do not use `BandV`, `BandG` and `BandHp` interchangeably for a V-band visibility threshold.
|
||||
- `Near` searches at each record's own epoch: normalize mixed epochs/frames first and apply proper-motion propagation for exact observing-date selection.
|
||||
|
||||
## Finding Sirius and its rise time
|
||||
|
||||
```go
|
||||
@@ -60,7 +110,7 @@ func main() {
|
||||
|
||||
Catalog coordinates have epoch J2000. `RaDecByDate` applies proper motion, precession and nutation before date-specific rise/set, altitude or constellation calculations.
|
||||
|
||||
Proper motion is propagated in **Julian years** (365.25 days), and `RaDecByDate` converts the civil instant to TT before taking the epoch difference. Whenever a record carries a distance (`Pc > 0`), `RaDecByJde` advances a full **three-dimensional space motion**: proper motion supplies the tangential velocity and `RadVel` the line-of-sight component, the position vector is extrapolated linearly and its direction is taken. Records without a distance fall back to two dimensions, advancing only the two angular components, which is equivalent to treating the star as infinitely distant. The two differ by second-order terms only — the curvature of the great-circle path and the change in angular scale as radial motion alters the distance; even the fastest bright star moves less than about 0.09 arcseconds from it within 26 years.
|
||||
Proper motion is propagated in **Julian years** (365.25 days), and `RaDecByDate` converts the civil instant to TT before taking the epoch difference. Whenever a record carries a distance (`Pc > 0`), `RaDecByJde` advances a full **three-dimensional space motion**: proper motion supplies the tangential velocity and `RadVel` the line-of-sight component, the position vector is extrapolated linearly and its direction is taken. Records without a distance fall back to two dimensions, advancing only the two angular components, which is equivalent to treating the star as infinitely distant. The two differ only in second-order terms: three-dimensional propagation also changes the star's distance, so the apparent place is slightly smaller than in the two-dimensional case; even the fastest bright star moves less than about 0.09 arcseconds from it within 26 years.
|
||||
|
||||
## API Reference
|
||||
|
||||
@@ -70,11 +120,12 @@ Proper motion is propagated in **Julian years** (365.25 days), and `RaDecByDate`
|
||||
| Star catalog | `InitStarDatabase` / `StarDataByHR` / `StarDataByName` / `TopBrightStars` | Initialise the embedded catalog, look up by HR number or Chinese name, fetch the bright-star sample | HR `1-9110`; `Mag` is apparent magnitude |
|
||||
| Coordinate correction | `StarData.RaDecByDate` (`RaDecByJde` on the `basic` side) | Correct a J2000 position to the given instant | Returns RA/Dec in degrees |
|
||||
| Rise, set, culmination | `RiseTime` / `SetTime` / `CulminationTime` (`DownTime` is a deprecated alias of `SetTime`) | Rise, set and culmination instants | Civil instants; polar cases return sentinel errors |
|
||||
| Rise/set with weather | `RiseTimeWithAtmosphere` / `SetTimeWithAtmosphere` | Rise and set with measured pressure (hPa) and temperature (°C) | Refraction always applied |
|
||||
| Topocentric quantities | `Altitude` / `ApparentAltitude` / `Azimuth` / `Zenith` / `ApparentZenith` | (Apparent) altitude, azimuth, (apparent) zenith distance | Degrees |
|
||||
| Hour angle and parallactic angle | `HourAngle` / `ParallacticAngle` | Stellar hour angle, zenith-direction parallactic angle | Degrees |
|
||||
| Sidereal time | `MeanSiderealTime` / `ApparentSiderealTime` | Mean and apparent sidereal time | Hours |
|
||||
|
||||
The `star` package has no `...N` truncated entry points; truncated analytical series live on the matching functions of `sun`, `moon`, the planets and `coord`, where `n < 0` uses every built-in term and `n >= 0` truncates the series (see those manuals).
|
||||
The `star` package has no `...N` entry points; the `n`-parameterised evaluation functions live on the matching functions of `sun`, `moon`, the planets and `coord`, with the semantics described in those manuals (`n < 0` uses the full series; under the semianalytic model every `n >= 0` uses the fast table, and Legacy truncates by `n`).
|
||||
|
||||
The snippets below omit shared preamble variables: `date` (observation instant, civil time scale), `lon`/`lat` (observer longitude/latitude, east/north positive, degrees), `height` (observer height, **ellipsoidal**, metres), `aero` (whether refraction and apparent-radius corrections are applied).
|
||||
|
||||
@@ -100,7 +151,7 @@ bright, _ := star.TopBrightStars()
|
||||
fmt.Println(len(bright), bright[0].HR) // size of the bright-star sample and the leading HR number
|
||||
```
|
||||
|
||||
The catalog loads lazily behind `sync.Once`; `InitStarDatabase()` only warms it up. It is idempotent and surfaces the load error explicitly, and skipping it still works because the first lookup loads the catalog automatically.
|
||||
The catalog loads on first lookup; `InitStarDatabase()` only warms it up. It is idempotent and surfaces the load error explicitly, and skipping it still works because the first lookup loads the catalog automatically.
|
||||
|
||||
`TopBrightStars()` returns 169 built-in entries around magnitude 3 or brighter, roughly ordered from brighter to dimmer.
|
||||
|
||||
@@ -164,7 +215,7 @@ for _, s := range bright[:3] {
|
||||
}
|
||||
```
|
||||
|
||||
Run at `date = 2020-01-01 08:08:08 CST` from `115 E, 40 N` with pressure `1010 hPa` and temperature `10 C`, this prints:
|
||||
Run at `date = 2020-01-01 08:08:08 CST` from `115°E, 40°N` with pressure `1010 hPa` and temperature `10 °C`, this prints:
|
||||
|
||||
```text
|
||||
天狼 Canis Major mag=-1.46 alt=-30.180
|
||||
@@ -218,11 +269,11 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
2019-12-31 19:22:56.144202053 +0800 CST // Sirius rising
|
||||
2020-01-01 05:30:39.802506566 +0800 CST // Sirius setting
|
||||
2019-12-31 19:22:56.32125467 +0800 CST // Sirius rising
|
||||
2020-01-01 05:30:39.979518949 +0800 CST // Sirius setting
|
||||
大犬座 // constellation of Sirius
|
||||
6h3m46.61s // Vega right ascension in 13600 CE
|
||||
84°18′27.15″ // Vega declination in 13600 CE
|
||||
6h4m44.89s // Vega right ascension in 13600 CE
|
||||
84°19′13.71″ // Vega declination in 13600 CE
|
||||
天狼 Sirius -1.46 // first bright-star entry: Chinese name, common name, magnitude
|
||||
```
|
||||
|
||||
@@ -310,7 +361,6 @@ if errors.Is(err, star.ERR_STAR_NEVER_RISE) || errors.Is(err, star.ERR_STAR_NEVE
|
||||
In the catalog `PmRA`/`PmDec` are arcseconds per year, `RadVel`/`RotVel` are km/s and `Pc` is parsecs.
|
||||
- **Time scale**: every public time argument and return value is a civil instant (a UTC label, equal to UT1 before 1972-01-01); see [Time Scale Declaration](map-geojson.md#time-scale-declaration).
|
||||
- **Coordinate convention**: the catalog is J2000; `RaDecByDate` adds proper motion, precession and nutation. Skipping it puts rise/set and constellation lookups off by a large margin (a century of precession is a degree-level shift).
|
||||
- **Polar sentinel errors**: `RiseTime` returns `star.ERR_STAR_NEVER_RISE` during a polar night (the star stays below the horizon all day) and `SetTime` returns `star.ERR_STAR_NEVER_SET` during a midnight sun (it stays above it); test with `errors.Is`. `ERR_STAR_NEVER_DOWN` is a deprecated alias of `ERR_STAR_NEVER_SET`.
|
||||
- **Observer height**: `height` is ellipsoidal height in metres; when you only have orthometric height, add the geoid undulation first, as described under "Observer Height Convention" in the README.
|
||||
- **Refraction range**: the Saemundsson approximation behind `ApparentAltitude`/`ApparentZenith` only applies for true altitudes inside `(-5, 90)` degrees and contributes zero outside that range, so well below the horizon the apparent altitude equals the geometric one (the `alt=-30.180` above is the same value `Altitude` returns).
|
||||
|
||||
|
||||
+44
-40
@@ -4,13 +4,15 @@
|
||||
|
||||
`sun` and `moon` are the main chains; `lite/sun` and `lite/moon` are independent approximation implementations.
|
||||
|
||||
The numerical output blocks in this manual illustrate call shapes and come from runs with the default semianalytic model. The differences from Legacy are small: lunar event times by about `1-10 s`, lunar distance by about `5 km`, phase by about `1e-6`, solar and lunar angles by about `1e-5°` and rise/set instants by milliseconds, with `lite/*` unaffected. Use an explicit `Context` / `Instance` to reproduce a fixed model; see [Accuracy and performance](accuracy.md) for the quantified errors of both models.
|
||||
|
||||
Unless stated otherwise, angles are in degrees, apparent diameters and semidiameters are in arcseconds, `sun.EarthDistance` is in AU and `moon.EarthDistance` is in kilometers. Observing APIs generally use civil instants.
|
||||
|
||||
Apparent-solar-time results instead represent local solar readings; see [Time scales](timescale.md).
|
||||
|
||||
This manual covers the Sun and the Moon themselves: position, rise/set and culmination, topocentric quantities, phases and syzygies, perigee/apogee and nodes, maximum declinations, libration, apparent size, and physical ephemeris.
|
||||
|
||||
Eclipse geometry lives in [Solar and Lunar Eclipse Charts](eclipse.md#solar-and-lunar-eclipse-charts), occultations in [Lunar Occultation Charts](occultation.md#lunar-occultation-charts), and general topocentric and refraction conversions in [Coordinate Tools](coord.md).
|
||||
Eclipse geometry lives in [Eclipses and Transits](eclipse.md#solar-and-lunar-eclipse-charts), occultations in [Lunar Occultation Charts](occultation.md#lunar-occultation-charts), and general topocentric and refraction conversions in [Coordinate Tools](coord.md).
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -20,7 +22,7 @@ Eclipse geometry lives in [Solar and Lunar Eclipse Charts](eclipse.md#solar-and-
|
||||
- [moon](#moon)
|
||||
- [lite/sun](#litesun)
|
||||
- [lite/moon](#litemoon)
|
||||
- [Truncated ...N family](#truncated-n-family)
|
||||
- [The `...N` family](#the-n-family)
|
||||
- [Usage examples](#usage-examples)
|
||||
- [Today's sunrise, sunset and twilight](#todays-sunrise-sunset-and-twilight)
|
||||
- [Moonrise, moonset and the Moon's altitude now](#moonrise-moonset-and-the-moons-altitude-now)
|
||||
@@ -94,7 +96,7 @@ func main() {
|
||||
|
||||
## API Reference
|
||||
|
||||
The tables below group the exported entry points of all four packages. Most evaluation entry points also have a `...N` truncated variant, described together under [Truncated ...N family](#truncated-n-family).
|
||||
The tables below group the exported entry points of all four packages. Most evaluation entry points also have a `...N` variant, described together under [the `...N` family](#the-n-family).
|
||||
|
||||
Every `time.Time` parameter is an absolute instant, and `lon`/`lat` are east-positive and north-positive.
|
||||
|
||||
@@ -113,7 +115,7 @@ Every `time.Time` parameter is an absolute instant, and `lon`/`lat` are east-pos
|
||||
| `DownTime` | Sunset alias | deprecated; calls `SetTime` internally |
|
||||
| `CulminationTime` | Upper culmination | `time.Time` |
|
||||
| `MorningTwilight` / `EveningTwilight` | Morning / evening twilight | `(time.Time, error)`; the angle is usually -6 / -12 / -18 degrees |
|
||||
| `Altitude` / `Zenith` / `Azimuth` / `HourAngle` | Geometric altitude / zenith distance / azimuth / hour angle | degrees, topocentric |
|
||||
| `Altitude` / `Zenith` / `Azimuth` / `HourAngle` | Geometric altitude / zenith distance / azimuth / hour angle | degrees; `Altitude` / `Zenith` / `Azimuth` are topocentric, while `HourAngle` is geocentric apparent (without topocentric parallax) |
|
||||
| `ApparentAltitude` / `ApparentZenith` | Apparent altitude / apparent zenith distance | degrees; requires pressure in hPa and temperature in degrees Celsius |
|
||||
| `ParallacticAngle` | Parallactic angle (zenith direction angle) | degrees, signed |
|
||||
| `ApparentSolarTime` | Apparent solar time | `time.Time`, with the zone derived from longitude |
|
||||
@@ -178,7 +180,7 @@ Planet targets use constants `OccultationMercury` through `OccultationNeptune`.
|
||||
| `TrueRa` / `TrueDec` / `TrueRaDec` | Lightweight true equatorial coordinates | degrees, geocentric |
|
||||
| `ApparentRa` / `ApparentDec` / `ApparentRaDec` | Lightweight apparent equatorial coordinates | degrees, geocentric |
|
||||
| `Distance` | Lightweight Earth-Sun distance | AU |
|
||||
| `HourAngle` / `Azimuth` / `Altitude` / `Zenith` | Lightweight hour angle / azimuth / altitude / zenith distance | degrees, topocentric |
|
||||
| `HourAngle` / `Azimuth` / `Altitude` / `Zenith` | Lightweight hour angle / azimuth / altitude / zenith distance | degrees; all use the geocentric apparent position, ignoring solar parallax of about 8.8 arcseconds |
|
||||
| `RiseTime` / `SetTime` | Lightweight sunrise / sunset | `(time.Time, error)` |
|
||||
| `ERR_SUN_NEVER_RISE` / `ERR_SUN_NEVER_SET` | Polar night / polar day | error values |
|
||||
|
||||
@@ -194,11 +196,11 @@ Planet targets use constants `OccultationMercury` through `OccultationNeptune`.
|
||||
| `RiseTime` / `SetTime` | Lightweight moonrise / moonset | `(time.Time, error)` |
|
||||
| `ERR_MOON_NEVER_RISE` / `ERR_MOON_NEVER_SET` / `ERR_NOT_TODAY` | Polar night / polar day / event not on the queried date | error values |
|
||||
|
||||
### Truncated ...N family
|
||||
### The `...N` family
|
||||
|
||||
Most evaluation entry points of `sun` and `moon` have a `...N` variant: the name is the base name plus `N`, one extra `n int` parameter is appended, and the return shape is unchanged.
|
||||
|
||||
`n < 0` keeps every analytical term embedded in this repository and matches the non-`N` version; `n >= 0` truncates the series to `n` terms, which is useful for performance comparison, bulk coarse evaluation, and error-sensitivity experiments.
|
||||
`n < 0` selects the full series and matches the non-`N` version. Default builds use to the semianalytic model, where all `n >= 0` select one fixed fast table: increasing `n` does not add terms. Legacy shortens its series according to `n`. Neither changes units or time zones; measured errors are in [Accuracy and performance](accuracy.md).
|
||||
|
||||
- sun: `TrueLoN`, `TrueBoN`, `AltitudeN`, `ZenithN`, `AzimuthN`, `HourAngleN`, `ParallacticAngleN`, `ApparentAltitudeN`, `ApparentZenithN`, `DiameterN`, `SemidiameterN`, `PhysicalN`, `RiseTimeN`, `SetTimeN`, `DownTimeN`, `CulminationTimeN`, `MorningTwilightN`, `EveningTwilightN`, `ApparentSolarTimeN`
|
||||
- moon: `TrueLoN`, `TrueBoN`, `AscendingNodeN`, `DescendingNodeN`, `DiameterN`, `SemidiameterN`, `PhysicalN`, `TopocentricPhysicalN`, `BrightLimbPositionAngleN`, `TopocentricBrightLimbPositionAngleN`
|
||||
@@ -294,7 +296,7 @@ delta dRA=0.5021 dDec=-0.3978
|
||||
280.8950939694744 -23.05840775453492
|
||||
```
|
||||
|
||||
The Moon is close, so geocentric and topocentric positions differ by half a degree (here `0.50` degrees in right ascension and `0.40` in declination), together more than one lunar apparent diameter; anything an observer sees must go through `moon.ApparentRaDec`. The Sun, 1 AU away, has negligible parallax and is exposed geocentrically only (of course, you can also call the topocentric coordinate conversion entry points to get a topocentric position =-=). The conventions are in [Observing-angle semantics](#observing-angle-semantics).
|
||||
The Moon is close, so geocentric and topocentric positions differ by half a degree (here `0.50` degrees in right ascension and `0.40` in declination), together more than one lunar apparent diameter; anything an observer sees must go through `moon.ApparentRaDec`. The Sun, 1 AU away, has negligible parallax and is exposed geocentrically only; convert with `coord.TopocentricEquatorial` when a topocentric position is needed. The conventions are in [Observing-angle semantics](#observing-angle-semantics).
|
||||
|
||||
### Main chain vs lite
|
||||
|
||||
@@ -312,9 +314,9 @@ fmt.Println(litesun.RiseTime(date, lon, lat, height, true)) // l
|
||||
2020-01-01 07:49:51.69717729 +0800 CST <nil>
|
||||
```
|
||||
|
||||
The lightweight chains mirror the main-chain shapes but not its accuracy: at this instant the altitude differs by about `0.0027` degrees and the phase by `0.0022`.
|
||||
The lightweight chains mirror the main-chain shapes but not its accuracy: in the output above the main chain and `lite/*` differ by about `0.0027` degrees in altitude and `0.0022` in phase.
|
||||
|
||||
Over 2026 at 8 sites the mean absolute error of `lite/sun` sunrise is `0.02 min` (P95 `0.04 min`, max `0.31 min`), of `lite/moon` moonrise `0.28 min` (P95 `0.57 min`, max `1.44 min`), and `lite/moon` `Phase()` peaks at `0.00243`.
|
||||
Historical comparisons against Legacy over 2026 at 8 sites give a mean absolute difference of `0.02 min` for `lite/sun` sunrise (P95 `0.04 min`, max `0.31 min`) and `0.28 min` for `lite/moon` moonrise (P95 `0.57 min`, max `1.44 min`); the maximum `lite/moon` `Phase()` difference is `0.00243`. These are model-to-model differences, not differences against an independent ephemeris.
|
||||
|
||||
The full comparison and the truncation errors are under [Differences from the main chain and error levels](#differences-from-the-main-chain-and-error-levels) and in [Lite chains](accuracy.md#lite-lightweight-chains).
|
||||
|
||||
@@ -384,14 +386,14 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
2020-01-01 07:22:27.960488498 +0800 CST <nil>
|
||||
2020-01-01 07:49:52.413689196 +0800 CST <nil>
|
||||
2020-01-01 12:47:35.933117866 +0800 CST
|
||||
2020-01-01 17:45:09.188657999 +0800 CST <nil>
|
||||
2020-01-01 18:12:33.624035418 +0800 CST <nil>
|
||||
2020-01-01 11:52:49.860912859 +0800 CST <nil>
|
||||
2020-01-01 17:36:48.811488747 +0800 CST
|
||||
2020-01-01 23:26:49.313553571 +0800 CST <nil>
|
||||
2020-01-01 07:22:28.91963303 +0800 CST <nil>
|
||||
2020-01-01 07:49:53.401680886 +0800 CST <nil>
|
||||
2020-01-01 12:47:35.933721363 +0800 CST
|
||||
2020-01-01 17:45:08.557346463 +0800 CST <nil>
|
||||
2020-01-01 18:12:33.02157104 +0800 CST <nil>
|
||||
2020-01-01 11:52:50.013020932 +0800 CST <nil>
|
||||
2020-01-01 17:36:48.954946696 +0800 CST
|
||||
2020-01-01 23:26:49.448562562 +0800 CST <nil>
|
||||
```
|
||||
|
||||
### Sun and Moon position
|
||||
@@ -444,16 +446,16 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
280.01526210031136
|
||||
280.0152652754265
|
||||
23.4362178391013
|
||||
RA: 18h43m34.82s Dec: -23°3′30.27″
|
||||
RA: 18h43m34.82s Dec: -23°3′30.20″
|
||||
Sagittarius
|
||||
Azimuth: 120.19477090015224 Altitude: 2.4014437419430097 Zenith: 87.59855625805699
|
||||
0.983292937163176
|
||||
RA: 23h18m56.24s Dec: -10°20′54.42″
|
||||
Azimuth: 120.19442333006145 Altitude: 2.398445319378493 Zenith: 87.6015546806215
|
||||
0.9832929289388871
|
||||
RA: 23h18m56.20s Dec: -10°20′54.64″
|
||||
Aquarius
|
||||
Azimuth: 67.63889332004852 Altitude: -45.34916937173283 Zenith: 135.34916937173284
|
||||
404238.6096080479
|
||||
Azimuth: 67.63846015645981 Altitude: -45.349642390635466 Zenith: 135.34964239063547
|
||||
404243.3922335782
|
||||
```
|
||||
|
||||
## The Sun
|
||||
@@ -486,10 +488,10 @@ RA: 281.6786097810291 Dec: -23.00369182413533
|
||||
23.438148552330773 280.83169623098 -0.08703499790144194
|
||||
```
|
||||
|
||||
Every entry point in this family also has a `...N` truncated variant. The comparison below truncates at `n = 8`; with `n < 0` the result matches the non-`N` version exactly:
|
||||
Every entry point in this family also has a `...N` variant. Here `n = 8` selects the semianalytic fast table (series truncation in Legacy); `n < 0` matches the non-`N` version exactly. The output below comes from the default semianalytic model:
|
||||
|
||||
```go
|
||||
// n<0 keeps every embedded VSOP term; n>=0 truncates the series.
|
||||
// n<0 selects the full series; n>=0 selects fast, or Legacy truncation.
|
||||
fmt.Println(sun.TrueLo(date), sun.TrueLoN(date, 8))
|
||||
fmt.Println(sun.Altitude(date, lon, lat), sun.AltitudeN(date, lon, lat, 8))
|
||||
fmt.Println(sun.Diameter(date), sun.DiameterN(date, 8))
|
||||
@@ -721,12 +723,12 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
0.30004130960877884 // about 30% of the lunar disk is illuminated
|
||||
0.3000398794307665 // about 30% of the lunar disk is illuminated
|
||||
上峨眉月 // Chinese phase description
|
||||
2020-01-25 05:41:58.271192908 +0800 CST // next new moon
|
||||
2020-01-03 12:45:23.229190707 +0800 CST // next first quarter
|
||||
2020-01-11 03:21:17.159625291 +0800 CST // next full moon
|
||||
2020-01-17 20:58:23.396406769 +0800 CST // next last quarter
|
||||
2020-01-25 05:41:59.860723614 +0800 CST // next new moon
|
||||
2020-01-03 12:45:24.51480031 +0800 CST // next first quarter
|
||||
2020-01-11 03:21:18.524373471 +0800 CST // next full moon
|
||||
2020-01-17 20:58:24.686442017 +0800 CST // next last quarter
|
||||
```
|
||||
|
||||
`Last*`, `Closest*`, and the decimal-year anchors `ShuoYue` / `FullMoon` return UTC. `ClosestConjunctionWithPlanet` finds the closest Moon-planet conjunction, with the target given by a `ConjunctionPlanet` constant:
|
||||
@@ -907,7 +909,7 @@ Output:
|
||||
|
||||
## Lite chains
|
||||
|
||||
`lite/sun` and `lite/moon` are independent approximation implementations: they do not depend on VSOP87 or the main chain's ELP2000/82 series, they target CPU- and memory-constrained environments, and their calling style matches the main chain.
|
||||
`lite/sun` and `lite/moon` are independent approximations: they use neither the semianalytic nor Legacy tables and do not follow global model switches. They target CPU- and memory-constrained environments, and their calling style matches the main chain.
|
||||
|
||||
Rise/set search uses fixed-step scanning plus bisection, without the main chain's high-precision nutation iteration.
|
||||
|
||||
@@ -1000,11 +1002,11 @@ Output:
|
||||
|
||||
The lite chains differ from the main chain in implementation and accuracy while keeping nearly the same interface shapes.
|
||||
|
||||
Pure evaluation entry points such as position and phase run about `8.3-27.3x` faster than the main chain, and rise/set entry points about `1.0-3.7x`, with zero heap allocation in the computation path; the rise/set scan step is `30` minutes for `lite/sun` and `15` minutes for `lite/moon`.
|
||||
Performance depends on the main-chain model, epoch cache and whether a search is required; there is no universal speedup factor. Lite's listed benchmarks allocate zero times, and rise/set scan every 30/15 minutes. Current model-specific measurements are in [Accuracy and Performance](accuracy.md#performance).
|
||||
|
||||
Errors against `sun` / `moon` (year 2026, 8 sites) are listed below, with data from [Lite lightweight chains](accuracy.md#lite-lightweight-chains):
|
||||
The following are historical comparisons against the old Legacy `sun` / `moon` chain (2026, 8 sites), not a new semianalytic validation; see [Lite lightweight chains](accuracy.md#lite-lightweight-chains):
|
||||
|
||||
| Capability | Mean absolute error | P95 | Max absolute error |
|
||||
| Quantity | Mean absolute error | P95 | Max absolute error |
|
||||
| --- | --- | --- | --- |
|
||||
| `lite/sun` sunrise | `0.02 min` | `0.04 min` | `0.31 min` |
|
||||
| `lite/sun` sunset | `0.02 min` | `0.06 min` | `0.35 min` |
|
||||
@@ -1015,7 +1017,7 @@ Errors against `sun` / `moon` (year 2026, 8 sites) are listed below, with data f
|
||||
| `lite/moon` geocentric longitude | `2.41'` | `6.82'` | `9.91'` |
|
||||
| `lite/moon` geocentric latitude | `0.87'` | `1.83'` | `2.92'` |
|
||||
|
||||
Neither package provides a `...N` truncated family. On the solar side the polar-night / polar-day errors are `ERR_SUN_NEVER_RISE` / `ERR_SUN_NEVER_SET`; on the lunar side, besides `ERR_MOON_NEVER_RISE` / `ERR_MOON_NEVER_SET`, there is `ERR_NOT_TODAY`, with the same semantics as the main chain.
|
||||
Neither package provides a `...N` family. On the solar side the polar-night / polar-day errors are `ERR_SUN_NEVER_RISE` / `ERR_SUN_NEVER_SET`; on the lunar side, besides `ERR_MOON_NEVER_RISE` / `ERR_MOON_NEVER_SET`, there is `ERR_NOT_TODAY`, with the same semantics as the main chain.
|
||||
|
||||
## Parameter and result conventions
|
||||
|
||||
@@ -1038,6 +1040,8 @@ The full UT1 and chart-label declaration is in the [Time Scale Declaration](map-
|
||||
|
||||
- The `height` of the rise/set entry points is the observer elevation interpreted as ellipsoidal (geodetic) height in meters, not orthometric height; see [Observer Height Convention](coord.md#observer-height)
|
||||
- `aero = true`: upper-limb crossing with dynamic standard refraction and the instantaneous semidiameter; `aero = false`: only the geometric center crossing
|
||||
- Rise/set refraction uses the standard atmosphere (1010 hPa, 10 °C) by default. For measured weather use `sun.RiseTimeWithAtmosphere` / `SetTimeWithAtmosphere` and `moon.RiseTimeWithAtmosphere` / `SetTimeWithAtmosphere`, passing pressure (hPa) and temperature (°C) on each call; refraction is always applied. Existing APIs are unchanged
|
||||
- Body-independent true/apparent altitude conversion lives in `formula`: `formula.ApparentAltitude`, `formula.TrueAltitude`, `formula.RefractionFromTrueAltitude`, `formula.RefractionFromApparentAltitude`
|
||||
- `pressureHPa` and `temperatureC` of `ApparentAltitude` / `ApparentZenith` are the observed pressure (hPa) and temperature (degrees Celsius)
|
||||
|
||||
### Zero values and out-of-range
|
||||
@@ -1046,12 +1050,12 @@ The full UT1 and chart-label declaration is in the [Time Scale Declaration](map-
|
||||
- When no twilight exists, they return `ERR_TWILIGHT_NOT_EXISTS`
|
||||
- When a lunar rise/set event falls outside the queried date, they return `ERR_NOT_TODAY`, matching the "moonrise/moonset are computed for the queried civil date" semantics above
|
||||
- `DownTime` / `DownTimeN` are deprecated aliases of `SetTime`, and `ERR_SUN_NEVER_DOWN` / `ERR_MOON_NEVER_DOWN` are deprecated aliases of the polar-day errors; new code should not use them
|
||||
- In `...N`, `n < 0` uses every embedded term and `n >= 0` truncates; `n` only changes the series length, never the returned unit or time zone
|
||||
- In `...N`, `n < 0` selects the full series; all `n >= 0` select the same semianalytic fast table, while Legacy truncates according to `n`. Units and time zones never change
|
||||
|
||||
### Accuracy and scope
|
||||
|
||||
The Sun and planets use embedded VSOP87 analytical terms and the Moon uses an embedded ELP2000/82-style truncated series, covering roughly 4000 years around J2000 with no external ephemeris files.
|
||||
Default builds use the embedded semianalytic model. The VSOP87 / ELP2000/82-style series remain as Legacy; neither requires external files. An available range is not the same as a measured range, and a measured error only holds for the measured samples.
|
||||
|
||||
Truncation errors, the lunar chain's capability boundaries, and the lite chains' quantified errors are documented in [Scope And Accuracy](accuracy.md).
|
||||
Model-specific DE440 / DE441 Sun and Moon comparisons, fast-table errors and Lite limitations are documented in [Accuracy and performance](accuracy.md).
|
||||
|
||||
The solar and lunar entry points in this manual suit calendars, observing aids, outreach, and amateur prediction; spacecraft navigation, precise occultation prediction, and rigorous dynamical integration require a professional ephemeris such as JPL DE.
|
||||
|
||||
+10
-12
@@ -118,7 +118,7 @@ p := dial.ShadowPointByHourAngleDeclination(-45, 23.44)
|
||||
fmt.Println(p.X, p.Y, p.Illuminated)
|
||||
```
|
||||
|
||||
The four `PlanarDial` fields are latitude, plate-normal azimuth, normal zenith distance and stylus length. `Geometry` returns the dial centre (where the polar stylus is fixed), the polar-stylus length and its angle to the plate; `ShadowPointByHourAngleDeclination` takes a signed hour angle and the solar declination, while `ShadowPointAt` takes the instant and longitude instead.
|
||||
The four `PlanarDial` fields are latitude, plate-normal azimuth, normal zenith distance and stylus length. `Geometry` returns the dial center (where the polar stylus is fixed), the polar-stylus length and its angle to the plate; `ShadowPointByHourAngleDeclination` takes a signed hour angle and the solar declination, while `ShadowPointAt` takes the instant and longitude instead.
|
||||
|
||||
### Plate illumination intervals
|
||||
|
||||
@@ -145,7 +145,7 @@ segsAt := dial.DeclinationCurveAt(date, 1.0)
|
||||
fmt.Println(len(segs), len(segsAt))
|
||||
```
|
||||
|
||||
A time line projects the equal-instant points of a mean-solar-time line straight onto the dial as `TimeLineSample` values; a declination curve samples the plate for a fixed declination or for the declination of the day, and each segment's `Interval` is the usable hour-angle interval described above.
|
||||
A time line projects the equal-instant points of a mean-solar-time line straight onto the dial as `TimeLineSample` values; a declination curve samples the plate for a fixed declination or for the declination of the day, and each segment's `Interval` is the usable hour-angle interval for that declination.
|
||||
|
||||
### Equatorial, horizontal and vertical dials
|
||||
|
||||
@@ -165,7 +165,7 @@ Each constructor sets the plate normal differently; when drawing, read it from t
|
||||
| `HorizontalDial` | `180°` | `0°` (normal points at the zenith; x east, y north) |
|
||||
| `EquatorialNorthDial` | `0°` | `90° - latitude` |
|
||||
| `EquatorialSouthDial` | `180°` | `90° + latitude` |
|
||||
| `VerticalDial` | argument normalised to `[0°, 360°)` | `90°` |
|
||||
| `VerticalDial` | argument normalized to `[0°, 360°)` | `90°` |
|
||||
|
||||
In the northern hemisphere the north-face equatorial dial serves the spring/summer half-year (positive solar declination) and the south face the autumn/winter half-year.
|
||||
|
||||
@@ -178,9 +178,9 @@ For `VerticalDial` the normal azimuth runs from north toward east, so a south-fa
|
||||
| `PlanarShadowPoint` | `X` / `Y` | Shadow-point coordinates in the stylus-length unit |
|
||||
| | `DenominatorQ` | Projection denominator; approaching zero means the shadow runs to infinity |
|
||||
| | `SunAboveHorizon` / `PlaneIlluminated` / `Illuminated` | Sun above the horizon, plate lit, and the final combined test |
|
||||
| `PlanarGeometry` | `CenterX` / `CenterY` | Dial centre (where the polar stylus is fixed) |
|
||||
| `PlanarGeometry` | `CenterX` / `CenterY` | Dial center (where the polar stylus is fixed) |
|
||||
| | `PolarStylusLength` / `PolarStylusPlaneAngle` | Polar-stylus length and its angle to the plate |
|
||||
| | `HasFiniteCenter` | False when the centre degenerates to infinity; the related quantities are `NaN` |
|
||||
| | `HasFiniteCenter` | False when the center degenerates to infinity; the related quantities are `NaN` |
|
||||
| `HourAngleInterval` | `Start` / `End` | Signed hour-angle interval in degrees, `Start <= End` |
|
||||
| `TimeLineSample` | `Date` / `Declination` / `HourAngle` / `Point` | Instant, solar declination, apparent hour angle and the shadow point |
|
||||
| `DeclinationCurveSegment` | `Declination` / `Interval` / `Samples` | Segment declination, usable hour-angle interval and sample chain |
|
||||
@@ -214,8 +214,8 @@ func main() {
|
||||
Output:
|
||||
|
||||
```text
|
||||
2026-06-21 09:34:10.438158222 +0805 LTZ
|
||||
hour angle=-36.456508 line@9am=-27.405871 line@now=-20.959182
|
||||
2026-06-21 09:34:10.404156224 +0805 LTZ
|
||||
hour angle=-36.456649 line@9am=-27.405871 line@now=-20.959281
|
||||
```
|
||||
|
||||
The zone in the first line is a synthetic local apparent solar time zone (`+08:05`, `LTZ`, for longitude `121.4737`), so the printed value already shows how far local apparent solar time is from clock time.
|
||||
@@ -239,7 +239,7 @@ decl=23.44 usable=-105.24..105.24 samples=211
|
||||
2 -37.92998436772365
|
||||
```
|
||||
|
||||
The first line says that with the solar declination at `23.44` degrees, a horizontal dial at latitude `31.2304` is usable over hour angles `[-105.24, +105.24]` (about 14 hours) sampled at 211 points; the second gives the two samples of the "local mean solar time 9:30" time line and the first hour angle. Together the two quantities are enough to draw a horizontal dial with its usable range marked.
|
||||
The first line says that with the solar declination at `23.44` degrees, a horizontal dial at latitude `31.2304` is usable over hour angles `[-105.24, +105.24]` (about 14 hours) sampled at 211 points; the second gives the two samples of the "local mean solar time 9:30" time line and the first hour angle. The usable interval can be used directly as the drawing clip range for the time lines.
|
||||
|
||||
### Common pitfalls
|
||||
|
||||
@@ -323,7 +323,7 @@ fmt.Println(v.PlaneNormalAzimuth, v.PlaneNormalZenithDistance)
|
||||
180 90
|
||||
```
|
||||
|
||||
- **Degenerate case**: when the plate normal is perpendicular to the polar axis (equivalently the polar stylus is parallel to the plate), `Geometry().HasFiniteCenter` is `false`, `CenterX`/`CenterY`/`PolarStylusLength` are `NaN` and `PolarStylusPlaneAngle` is 0 - latitude `45`, normal azimuth `180` and normal zenith distance `45` is exactly such a point, and nothing drawn relative to the centre is usable.
|
||||
- **Degenerate case**: when the plate normal is perpendicular to the polar axis (equivalently the polar stylus is parallel to the plate), `Geometry().HasFiniteCenter` is `false`, `CenterX`/`CenterY`/`PolarStylusLength` are `NaN` and `PolarStylusPlaneAngle` is 0 - latitude `45`, normal azimuth `180` and normal zenith distance `45` is exactly such a point, and nothing drawn relative to the center is usable.
|
||||
- **Per-face conventions**: a horizontal plate's normal points at the zenith (zenith distance `0`); a vertical plate has zenith distance `90` and its `planeNormalAzimuth` is the **normal** direction (south-facing wall `180`, east-facing wall `90`); the equatorial north and south faces use `90 - latitude` and `90 + latitude`.
|
||||
|
||||
The full table of the four constructors is under [Equatorial, horizontal and vertical dials](#equatorial-horizontal-and-vertical-dials).
|
||||
@@ -339,9 +339,7 @@ fmt.Println(v.PlaneNormalAzimuth, v.PlaneNormalZenithDistance)
|
||||
|
||||
`ZoneTimePoint` / `ZoneTimeLine` ignore the hour, minute and second of `date`, keep only its date and zone, and substitute the `zoneTimeHours` argument for the clock reading. Passing the wrong convention shifts the whole time line.
|
||||
- **Three booleans gate a valid shadow**: `SunAboveHorizon` says the Sun is up, `PlaneIlluminated` says the plate faces the Sun, and `Illuminated` is the final test requiring both; draw a shadow only when `Illuminated` is true.
|
||||
- **Degenerate case**: when the plate normal is perpendicular to the polar axis (equivalently, when the polar stylus is parallel to the plate), `PlanarGeometry.HasFiniteCenter` is `false`, `CenterX`/`CenterY`/`PolarStylusLength` are `NaN` and `PolarStylusPlaneAngle` is `0` - the centre is at infinity and nothing drawn relative to it is usable.
|
||||
|
||||
Latitude `45`, normal azimuth `180` and normal zenith distance `45` is exactly such a point.
|
||||
- **Degenerate case**: when the plate normal is perpendicular to the polar axis, `HasFiniteCenter` is `false`, `CenterX`/`CenterY`/`PolarStylusLength` are `NaN`, the center is at infinity and nothing drawn relative to the center is usable; see [Degenerate geometry and per-face dial conventions](#degenerate-geometry-and-per-face-dial-conventions).
|
||||
- **`PlaneIlluminated` versus `Illuminated`**: the former is geometric only, the latter also requires the Sun above the horizon; use the latter when answering "how long is this dial usable in a day".
|
||||
|
||||
## Related manuals
|
||||
|
||||
@@ -82,6 +82,14 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
UTC: 2026-04-01T00:00:00Z
|
||||
TT: 2026-04-01 00:01:09.184014
|
||||
UT1: 2026-04-01 00:00:00.051015
|
||||
DUT1: 0.051000 s
|
||||
UTC from TT: 2026-04-01T00:00:00Z
|
||||
```
|
||||
|
||||
Conversions pass through floating-point Julian days, so round trips can have small rounding differences. The TT/UT1 display formats omit `Z` to avoid presenting them as UTC timestamps.
|
||||
|
||||
## Julian-day conversion API
|
||||
@@ -129,7 +137,7 @@ A single `float64` JD has a resolution of about 40 µs near modern dates; the `t
|
||||
|
||||
## ΔT models
|
||||
|
||||
The default combines SMH2016 and Morrison 2021 splines and long-term extrapolation, using a monthly observed ΔT table wherever available. The table currently extends to September 1, 2026; the last point is a rapid observation, not yet a final solution. Constant endpoint adjustments keep extrapolation continuous.
|
||||
The default combines SMH2016 and Morrison 2021 splines and long-term extrapolation, using a monthly observed ΔT table wherever available. The table currently extends to October 1, 2026; the September 1 point is final, while the last point is a preliminary extension. Constant endpoint adjustments keep extrapolation continuous.
|
||||
|
||||
`DeltaT` returns `NaN` outside ±40000 years.
|
||||
|
||||
@@ -173,6 +181,12 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
```text
|
||||
smh2016 86.98252070239269
|
||||
espenak_meeus_2006 202.7448354310582
|
||||
nasa_canon_2006 202.7448354310582
|
||||
```
|
||||
|
||||
Future ΔT is uncertain. The current Espenak–Meeus 2006 and default models differ by about 11, 21, 116 and 275 seconds in 2035, 2050, 2100 and 2200. This affects future eclipse times and ground paths.
|
||||
|
||||
Match ΔT models before comparing eclipse catalogs.
|
||||
@@ -194,19 +208,27 @@ These settings affect subsequent calculations throughout the process. Configure
|
||||
|
||||
## UTC assumptions beyond the observed interval
|
||||
|
||||
`SetTimeScaleFuturePolicy` and `GetTimeScaleFuturePolicy` select the civil-time conversion policy, defaulting to `TimeScaleLeapSecond`. All policies apply only after the observed interval except `TimeScaleUT1Civil`, which replaces the scale throughout the timeline. An explicit TT−UTC override takes precedence from 1972 onward. This setting is separate from the output-scale options `TimeScaleUTC` and `TimeScaleUT1`.
|
||||
> **UTC stops inserting leap seconds on 20 May 2027 (leap hour replaces leap second)**
|
||||
>
|
||||
> On 15 October 2026 the 28th General Conference on Weights and Measures (CGPM) adopted [Resolution 5, "On the technical actions needed to ensure the continuity of UTC"](https://www.bipm.org/en/cgpm-2026/resolution-5) (`Resolution-CGPM-28-5`, DOI `10.59161/CGPM2026RES5E`): **continuous UTC becomes effective on 20 May 2027** and the maximum value of `|UT1−UTC|` is widened from 0.9 seconds to **3600 seconds (1 hour)**.
|
||||
>
|
||||
> [IERS Bulletin C 72 (2026-07-06)](https://datacenter.iers.org/data/latestVersion/bulletinC.txt) confirms that **no leap second will be introduced at the end of December 2026**; the leap-second mechanism ends on 2027-05-20, so the possible insertion point at the end of June 2027 will not carry one either.
|
||||
>
|
||||
> This library therefore changes the default policy beyond the built-in window (which ends 2026-10-01) to `TimeScaleLeapHour`: 1972–2026 still uses the definitive leap-second table and those historical values are unchanged; past the window TT−UTC stays at 69.184 seconds until `|UT1−UTC|` approaches one hour, centuries from now. The 1900–2026 span of the modern lunisolar fast tables is consequently bit-identical as well.
|
||||
|
||||
`SetTimeScaleFuturePolicy` and `GetTimeScaleFuturePolicy` select the civil-time conversion policy, defaulting to `TimeScaleLeapHour`. All policies apply only after the observed interval except `TimeScaleUT1Civil`, which replaces the scale throughout the timeline. An explicit TT−UTC override takes precedence from 1972 onward. This setting is separate from the output-scale options `TimeScaleUTC` and `TimeScaleUT1`.
|
||||
|
||||
| Policy | Assumption |
|
||||
| --- | --- |
|
||||
| `TimeScaleLeapSecond` (zero value, default) | Applies the fewest integer-second corrections to the final TT−UTC offset to bring extrapolated DUT1 within ±0.9 seconds |
|
||||
| `TimeScaleLeapHour` (zero value, default) | Applies the fewest whole-hour corrections to bring DUT1 within ±3600 seconds; matches the continuous UTC effective 2027-05-20 |
|
||||
| `TimeScaleLeapSecond` | Applies the fewest integer-second corrections to the final TT−UTC offset to bring extrapolated DUT1 within ±0.9 seconds; the definitive 1972–2026 convention, kept for reproduction and comparison |
|
||||
| `TimeScaleAssumeUT1Tracking` | Holds the final observed DUT1 constant, allowing TT−UTC to follow extrapolated ΔT smoothly |
|
||||
| `TimeScaleFreezeUTCOffset` | Holds TT−UTC at its final built-in value, currently 69.184 seconds |
|
||||
| `TimeScaleLeapHour` | Applies the fewest whole-hour corrections to bring DUT1 within ±3600 seconds; intended for scenario calculations |
|
||||
| `TimeScaleUT1Civil` | Treats civil time as UT1 throughout the timeline, including the historical leap-table interval; DUT1 is zero unless explicitly overridden |
|
||||
|
||||
These are calculation assumptions, not predictions of future leap seconds or international decisions. Integer-second or hour corrections depend on ΔT at the queried instant; they do not track prior corrections or restrict steps to announcement calendar boundaries. Pointwise round trips need not be identical near a step. A stepping policy returns `NaN` when its ΔT is invalid or outside the model range, rather than falling back to a normal offset.
|
||||
The default policy `TimeScaleLeapHour` follows the continuous UTC that takes effect on 2027-05-20, so it is no longer a scenario assumption; `TimeScaleLeapSecond` is kept to reproduce the definitive 1972–2026 convention or for comparison. Whole-hour corrections depend on ΔT at the queried instant; they do not track prior corrections or restrict steps to announcement calendar boundaries. Pointwise round trips need not be identical near a step. A stepping policy returns `NaN` when its ΔT is invalid or outside the model range, rather than falling back to a normal offset.
|
||||
|
||||
The default does not guarantee an error below 0.9 seconds against real future UTC. Precise civil timing requires published data for the relevant date.
|
||||
Under `TimeScaleLeapHour` the `|UT1−UTC|` tolerance is 3600 seconds, so TT−UTC past the window stays at 69.184 seconds for a very long time; `TimeScaleLeapSecond` still extrapolates against ±0.9 seconds and reproduces the old one-to-two-year stepping, which no longer reflects reality. Precise civil timing requires published data for the relevant date.
|
||||
|
||||
## SVG, GeoJSON and KML labels
|
||||
|
||||
|
||||
Reference in New Issue
Block a user