feat: 引入可切换星历模型并扩展天象观测链路

- 新增半解析、旧模型、自定义与多源堆叠星历实例,提供 Context、Instance、SPK 读取及状态与视位置计算
- 统一太阳、月球和行星实例接口,扩展黄经穿越、留、相位、最近角距、偕日可见、任意高度升落、轨道节点及凌日等天象查询
- 完善日月食、月掩与凌日的月缘剖面、有限盘面接触、站心修正、局地可见性、全球路径和地图输出
- 新增 SVG、GeoJSON、KML 天象输出及凌日、月掩和月缘地形相关缓存与几何求解
- 新增恒星星表导入与查询,支持 CSV、Hipparcos、BSC5、Stellarium 等格式及名称、交叉标识、锥面检索和星等过滤
- 补充 IAU 2006、地球定向、折射消光、大气观测、轨道传播及半解析行星/月球数据支持
- 将默认未来民用时标切换为 TimeScaleLeapHour,更新 2027–2400 农历速查表、中文日期解析和时区边界处理
- 增加事件基线、跨模型回归、几何边界、月缘地形和输出契约测试,完善中英文手册、示例资源、NOTICE 与忽略规则
This commit is contained in:
2026-10-15 23:54:15 +08:00
parent 16c62a97d5
commit 044f2f4381
674 changed files with 62983 additions and 8396 deletions
+59 -9
View File
@@ -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).