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

601 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 月掩
[English](en/occultation.md) | [返回 README](../../README.md)
月掩接口位于 `moon`,按用户给定的目标搜索,不会遍历恒星表(常用被掩星表由调用方自行维护)。固定地点接口直接接收 `start`、`end`、经度、纬度和椭球高;全球路径接口返回 WGS84 经纬度采样,可继续交给 `moon/svg` 或 `geojson`或`kml`处理。
目标和接触语义分为两类:
- **恒星**按点光源处理,返回掩始 `Immersion`、掩甚 `Greatest` 和掩终 `Emersion`。
- **行星**按有限圆盘处理(纯圆),外切为 C1/C4,完全被月面覆盖时另有内切 C2/C3;偏掩和擦掩没有 C2/C3。行星半径取赤道本体半径,不含行星环、大气延伸和扁率。
- `FindBestStarOccultations` / `FindBestPlanetOccultations` 返回全球海平面几何掩甚点,不按地平线、月高、可见时长或食分评分;`VisibleAtGreatest` 仅报告该点的可见性。
- 搜索时间窗按掩甚时刻选择事件。命中后会返回完整接触时刻或完整全球路径,不会把结果裁剪到查询端点。
- 接触时刻按目标与月面边缘的站心几何求解,不加入大气折射。`MoonAltitudeAtGreatest` 是月心真高度,`VisibleAtGreatest` 表示它是否不低于几何地平线。
## 目录
- [简单示例:搜索指定地点的恒星月掩](#简单示例搜索指定地点的恒星月掩)
- [API 参考](#api-参考)
- [常用场景](#常用场景)
- [某地今晚有没有月掩](#某地今晚有没有月掩)
- [行星月掩与有限圆盘](#行星月掩与有限圆盘)
- [全球掩带图与详细版式](#全球掩带图与详细版式)
- [掠掩、等时线与掩带宽口径](#掠掩等时线与掩带宽口径)
- [恒星月掩](#恒星月掩)
- [搜索与路径采样选项](#搜索与路径采样选项)
- [路径算法与轮廓](#路径算法与轮廓)
- [掩甚时刻等时线](#掩甚时刻等时线)
- [行星月掩](#行星月掩)
- [月缘地形修正](#月缘地形修正)
- [月掩出图](#月掩出图)
- [全球掩带图](#全球掩带图)
- [详细版式](#详细版式)
- [站心事件图](#站心事件图)
- [精密月掩剖面图](#精密月掩剖面图)
- [时标口径与 UT1](#时标口径与-ut1)
## 简单示例:搜索指定地点的恒星月掩
```go
package main
import (
"fmt"
"log"
"time"
"b612.me/astro/moon"
)
func main() {
cst := time.FixedZone("CST", 8*3600)
start := time.Date(2025, 6, 5, 0, 0, 0, 0, cst)
end := start.AddDate(0, 0, 1)
target := moon.StarCoordinate{
ID: "HR 4799", RA: 189.1975, Dec: -5.831944444444, // 室女座25,进贤增九 和它滴赤经赤纬
Epoch: time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC), //赤经赤纬的历元信息,上面是J2000.0的坐标,若坐标是该时刻的视位置,必须同时把 Frame 设为 apparent_of_date
Frame: moon.CoordinateFrameJ2000,//坐标系,可选 icrs / j2000 / apparent_of_date
ProperMotionRACosDecMasPerYear: -28, //赤经方向自行,毫角秒/年
ProperMotionDecMasPerYear: -18, //赤纬方向自行,毫角秒/年
// 距离可选:不给就退回二维自行;给了DistanceLightYear(或ParallaxMas)就走三维空间运动,
// 此时RadialVelocityKmPerSecond才参与计算,不填按径向速度为0处理。
}
events, err := moon.FindStarOccultations(start, end, target,
121.56601, 6.80706, 0, moon.OccultationSearchOptions{})
if err != nil {
log.Fatal(err)
}
if len(events) == 0 {
fmt.Println("no occultation in this window")
return
}
for _, event := range events {
fmt.Println(event.Type, event.Immersion, event.Greatest, event.Emersion)
}
}
```
空切片表示搜索窗内没有命中事件,是正常结果。搜索按掩甚时刻归属窗口,返回的掩始、掩终可能在窗口之外。
## API 参考
后续片段沿用首例的目标与搜索窗口。SVG 调用使用导入别名 `moonsvg "b612.me/astro/moon/svg"`。
| 名称 | 用途 | 备注 |
| --- | --- | --- |
| `moon.FindStarOccultations` / `FindStarOccultationPaths` | 恒星月掩事件 / 全球路径 | 目标为 `moon.StarCoordinate` |
| `moon.FindPlanetOccultations` / `FindPlanetOccultationPaths` | 行星月掩事件 / 全球路径 | 按有限圆盘求解 |
| `moon.StarCoordinateFromStarData` | 由内置星表构造目标 | 需先加载星表 |
| `moonsvg.FindStarOccultationSVGs` / `FindPlanetOccultationSVGs` | 搜索并渲染全球图 | 返回 `([]string, error)` |
| `moonsvg.FindLocalStarOccultationSVGs` / `FindLocalPlanetOccultationSVGs` | 搜索并渲染站心图 | 同上 |
| `moonsvg.StarOccultationPathSVG` / `PlanetOccultationPathSVG` | 渲染已有全球路径 | 返回 `(string, error)` |
| `moonsvg.StarOccultationDetailedSVG` / `PlanetOccultationDetailedSVG` | 一页详细版式 | 固定正射球面 |
| `moon.StarOccultationLimbProfile` / `StarOccultationLimbProfileInUT1` | 沿星迹按月缘剖面精密求解,给出全部遮挡区间与带内采样 | 必须传入剖面;结果见 `StarOccultationLimbProfileResult` |
| `moonsvg.StarOccultationLimbProfileSVG` | 精密月掩剖面图 | 选项 `StarOccultationLimbProfileSVGOptions` |
| `moonsvg.StarOccultationSVGOptions` / `moonsvg.OccultationDetailedSVGOptions` | 出图选项(画布、投影、时标、标记步长) | 投影用 `MapProjection*`,时标用 `astro.TimeScale*` |
| `moon.OccultationMercury` … `moon.OccultationNeptune` | 行星目标常量 | 传给行星入口 |
| `moon.OccultationSearchOptions` / `moon.OccultationPathOptions` | 搜索与路径选项 | 路径选项含 `Step`、`TargetSpacingKM`、`GreatestTimeStep` |
## 常用场景
| 场景列表 | 入口 | 返回 |
| --- | --- | --- |
| 某地某晚有没有月掩 | `moon.FindStarOccultations(start, end, star, lon, lat, height, searchOptions)` | `[]moon.StarOccultationInfo` |
| 全球几何掩甚点 | `moon.FindBestStarOccultations(start, end, star, searchOptions)` | `[]moon.StarOccultationInfo` |
| 全球掩带几何 | `moon.FindStarOccultationPaths(start, end, star, pathOptions)` | `[]moon.StarOccultationPath` |
| 某一时刻的全球可见足迹 | `moon.StarOccultationFootprintAt(at, star)` | `moon.StarOccultationInstant` |
| 搜索并一步出全球掩带图 | `moonsvg.FindStarOccultationSVGs(start, end, star, pathOptions, svgOptions)` | `([]string, error)` |
| 已有路径只渲染 | `moonsvg.StarOccultationPathSVG(path, svgOptions)` | `(string, error)` |
| 一页详细版式 | `moonsvg.StarOccultationDetailedSVG(path, star, detailedOptions)` | `(string, error)` |
| 搜索并出指定地点的站心图 | `moonsvg.FindLocalStarOccultationSVGs(start, end, star, lon, lat, height, searchOptions, localOptions)` | `([]string, error)` |
| 渲染已有的固定地点事件 | `moonsvg.LocalStarOccultationSVG(info, star, localOptions)` | `(string, error)` |
| 只要站心轨迹几何数据(不出图) | `moon.StarOccultationDiagram(info, star, diagramOptions)` | `moon.StarOccultationDiagramResult` |
| 交给 GIS | `geojson.MarshalStarOccultation(path)` / `MarshalStarOccultationWithTimeMarkers(path, markerOptions)` / `MarshalStarOccultationFootprint(instant)` | `([]byte, error)` |
### 某地今晚有没有月掩
```go
events, err := moon.FindStarOccultations(start, end, target, 121.56601, 6.80706, 0, moon.OccultationSearchOptions{})
if err != nil {
panic(err)
}
for _, e := range events {
fmt.Println(e.Type, e.Immersion.Format("15:04:05.000"),
e.Greatest.Format("15:04:05.000"), e.Emersion.Format("15:04:05.000"))
}
```
```text
total 19:14:01.071 20:02:06.314 20:50:10.715
```
- `FindStarOccultations` 给站心接触时刻:`Immersion`(掩始)、`Greatest`(掩甚)、`Emersion`(掩终);`Type` 区分全掩与掠掩。
- 目标既可以直接给赤经赤纬,也可以先加载内置星表再用 `moon.StarCoordinateFromStarData` 转换;**搜索本身不加载星表**,只有调用星表接口时才加载。
- 只要全球结果、不要某地接触时,用 `FindStarOccultationPaths` 一步拿到路径。
### 行星月掩与有限圆盘
```go
start := time.Date(2025, 2, 1, 0, 0, 0, 0, cst)
events, err := moon.FindPlanetOccultations(start, start.Add(24*time.Hour), moon.OccultationSaturn,
104.52219613, 55.25401991, 0, moon.OccultationSearchOptions{})
if err != nil {
panic(err)
}
for _, e := range events {
fmt.Println(e.TargetID, e.Type, e.HasInternalContacts)
fmt.Println(e.ExternalImmersion.Format("2006-01-02 15:04:05.000 MST"), e.InternalImmersion.Format("2006-01-02 15:04:05.000 MST"))
fmt.Println(e.Greatest.Format("2006-01-02 15:04:05.000 MST"), e.InternalEmersion.Format("2006-01-02 15:04:05.000 MST"), e.ExternalEmersion.Format("2006-01-02 15:04:05.000 MST"))
}
```
```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
```
行星按**有限圆盘**求解接触:`HasInternalContacts` 为真时才有 C2/C3(内切),`OccultationPlanet` 决定圆盘半径;土星环既不参与接触计算、也不作为圆盘边界绘制。
### 全球掩带图与详细版式
```go
paths, err := moon.FindStarOccultationPaths(start, end, target,
moon.OccultationPathOptions{Step: 5 * time.Minute, TargetSpacingKM: 200})
if err != nil {
panic(err)
}
if len(paths) == 0 {
fmt.Println("no occultation path")
return
}
svg, err := moonsvg.StarOccultationPathSVG(paths[0], moonsvg.StarOccultationSVGOptions{
Width: 1200, Height: 800, Location: cst, Projection: moonsvg.MapProjectionSouthPolar,
})
detailed, detailErr := moonsvg.StarOccultationDetailedSVG(paths[0], target,
moonsvg.OccultationDetailedSVGOptions{Width: 1000, Height: 1414, Location: cst})
fmt.Println(err, len(svg), detailErr, len(detailed))
```
```text
<nil> 120119 <nil> 633489
```
- 掩带图返回 `(string, error)`,四档投影与日食图一致;详细版式固定正射球面、不接受其它投影,版面随画布长宽自动推导。
- 只渲染已有路径用 `StarOccultationPathSVG`;"搜索 + 渲染"一步到位用 `FindStarOccultationSVGs`(见上方两条链)。
- 画布下限:单独掩带图 `640×480`;详细版宽度下限是 `480`,但整幅版式实测要 `670×595` 以上才放得下地图、数据块与页脚,低于下限直接返回错误。
### 掠掩、等时线与掩带宽口径
- **掠掩没有中心线**:`HasTotalBand` 为假时全球图只画南北限,此情形不要假设存在中心线。
- **掩带宽口径**:图上标注的"掩带宽"是掩甚处南北限的地面间距 `GreatestLimitSeparationKM`(HR 4799 示例约 `3666.6 km`),与中心线横向宽度 `Greatest.WidthKM`(约 `3582.4 km`)口径不同、不可互换。
- **等时线**:要在路径层显式请求 `moon.OccultationPathOptions.GreatestTimeStep`;`moon/svg` 只绘制路径结果里已有的 `GreatestTimeContours`,位置由核心口径决定(对齐 UTC 整刻度)。
- **UT1 口径**:`TimeScale: astro.TimeScaleUT1` 出 UT1 读数与 DUT1 差值,此时 `Location` 必须为 UTC,配非 UTC 时区直接返回错误而不是画错图。
## 恒星月掩
> 图内与图注的时标声明见[时标声明](map-geojson.md#时标声明)。
恒星由调用者传入 `StarCoordinate`。`RA` / `Dec` 单位为度,`Epoch` 和 `Frame` 必填;自行单位为 `mas/year`,其中 `ProperMotionRACosDecMasPerYear` 使用星表常见的 `dRA*cos(Dec)` 口径。
| 字段 | 类型 | 零值 | 合法范围与报错 | 作用 |
| --- | --- | --- | --- | --- |
| `ID` | `string` | `""` | 无限制 | 展示标签,出现在结果和图题里,不参与任何计算 |
| `RA` | `float64` | — | 必填,`[0, 360)`,否则 `ErrInvalidOccultationInput` | 赤经,单位**度**(不是时分秒) |
| `Dec` | `float64` | — | 必填,`[-90, 90]` | 赤纬,单位度,北正南负 |
| `Epoch` | `time.Time` | `time.Time{}` | 零值报错 | 上面两个角度所属的历元时刻,比如J2000.0 |
| `Frame` | `moon.CoordinateFrame` | `""` | 只认 `icrs` / `j2000` / `apparent_of_date`,空值报错 | 参考系,见下 |
| `ProperMotionRACosDecMasPerYear` | `float64` | `0` | 必须是有限值 | 赤经方向自行,**mas/年**,口径是 `dRA·cos(Dec)` |
| `ProperMotionDecMasPerYear` | `float64` | `0` | 必须是有限值 | 赤纬方向自行,mas/年 |
| `ParallaxMas` | `float64` | `0` | 必须有限且 ≥ 0 | 周年视差,mas;`0` 表示未提供距离,此时退回二维自行并跳过视差修正 |
| `DistanceLightYear` | `float64` | `0` | 必须有限且 ≥ 0 | 距离,光年;`ParallaxMas` 的替代输入,仅当视差为 `0` 时生效 |
| `RadialVelocityKmPerSecond` | `float64` | `0` | 必须有限且 \|v\| ≤ 1000 | 径向速度,km/s;`0` 合法,只在距离已知时参与三维空间运动 |
距离的两种给法满足一条优先级:`ParallaxMas > 0` 时以它为准,否则由 `DistanceLightYear` 折算视差。给距离即启用三维空间运动;不给距离时自行只推进赤经赤纬两个角分量,`RadialVelocityKmPerSecond` 不参与。
`Epoch` 与 `Frame` 的额外说明:
- `j2000`:坐标是 J2000.0 平位置,`Epoch` 填 `2000-01-01 12:00 UTC`。库内岁差起点硬编码为 J2000.0,`Epoch` 只决定自行从哪一年开始外推;把非 J2000 历元填进来会让自行重复计一段。
- `icrs`:坐标是 ICRS 星表位置,先过一次 ~17 mas 的框架偏差矩阵;`Epoch` 填星表历元(Hipparcos `1991.25`、Gaia `2016.0`)。
- `apparent_of_date`:坐标是**该时刻的视位置**(已含岁差、章动、光行差),`Epoch` 必须填那一刻;库会在该时刻反解回平位置再向前传播。直接从星图软件(如stellarium)显示的"当前坐标"时使用这个参数。
可以直接构造坐标:
```go
target := moon.StarCoordinate{
ID: "HR 4799",
RA: 189.1975,
Dec: -5.831944444444,
Epoch: time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC),
Frame: moon.CoordinateFrameJ2000,
ProperMotionRACosDecMasPerYear: -28,
ProperMotionDecMasPerYear: -18,
}
```
也可以显式加载内置 9100 星表,再用 `StarCoordinateFromStarData` 转换。月掩搜索本身不会加载星表;只有调用 `star.InitStarDatabase`、`StarDataByName`、`StarDataByHR` 等星表接口时才会加载。
```go
package main
import (
"fmt"
"time"
"b612.me/astro/moon"
"b612.me/astro/star"
)
func main() {
cst := time.FixedZone("CST", 8*3600)
start := time.Date(2025, 6, 5, 0, 0, 0, 0, cst)
end := start.Add(24 * time.Hour)
if err := star.InitStarDatabase(); err != nil {
panic(err)
}
data, err := star.StarDataByName("进贤增九")
if err != nil {
panic(err)
}
target, err := moon.StarCoordinateFromStarData(data)
if err != nil {
panic(err)
}
events, err := moon.FindStarOccultations(
start, end, target,
121.56601, 6.80706, 0,
moon.OccultationSearchOptions{},
)
if err != nil {
panic(err)
}
for _, event := range events {
fmt.Println(event.TargetID, event.Type)
fmt.Println(
event.Immersion.Format("2006-01-02 15:04:05.000 MST"),
event.Greatest.Format("2006-01-02 15:04:05.000 MST"),
event.Emersion.Format("2006-01-02 15:04:05.000 MST"),
)
fmt.Printf("altitude=%.3f visible=%v\n", event.MoonAltitudeAtGreatest, event.VisibleAtGreatest)
}
paths, err := moon.FindStarOccultationPaths(
start, end, target,
moon.OccultationPathOptions{Step: 5 * time.Minute, TargetSpacingKM: 200},
)
if err != nil {
panic(err)
}
for _, path := range paths {
fmt.Println(
path.Start.Time.Format("2006-01-02 15:04:05.000 MST"),
path.Greatest.Time.Format("2006-01-02 15:04:05.000 MST"),
path.End.Time.Format("2006-01-02 15:04:05.000 MST"),
)
fmt.Printf("greatest=%.6f %.6f width=%.1fkm center=%d\n",
path.Greatest.Longitude, path.Greatest.Latitude,
path.Greatest.WidthKM, len(path.CenterLine))
}
}
```
输出结果:
```text
进贤增九 total
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
```
### 搜索与路径采样选项
`OccultationSearchOptions` 的零值采用默认步长和安全余量,`MaxEvents > 0` 限制返回数量。全球路径的采样由 `OccultationPathOptions` 控制:
| 字段 | 作用 |
| --- | --- |
| `Step` | 基础时间步长 |
| `TargetSpacingKM` | 按地面距离加密中心线;请求超出采样预算时返回 `ErrOccultationPathSamplingLimit` |
| `RiseSetStep` | 六类初掩、掩甚、终掩月升/月落阶段线的步长,零值为 5 分钟 |
| `DisableRiseSet` | 跳过六类升落阶段线 |
| `DisableFootprints` | 跳过密集瞬时足迹,以稀疏支撑样本生成紧凑掩带;保留中心线、边界和升落阶段线 |
| `IncludeFootprintTimeline` | 在紧凑掩带之外保留瞬时足迹序列 |
| `FootprintTimelineStep` | 上述瞬时足迹序列的采样步长 |
`GreatestLimitSeparationKM` 是结果中掩甚处南北限的地面间距,图中的“掩带宽”使用该值。它与 `Greatest.WidthKM` 定义不同,不能互换。
### 路径算法与轮廓
`OccultationPathOptions.Algorithm` 控制恒星和行星全球路径的星历分支:零值或 `moon.OccultationPathAlgorithmOptimized` 用 30 分钟节点的矢量插值近似星历;插值自检不通过、或时刻超出插值时间窗时,改用完整星历重算。`moon.OccultationPathAlgorithmExact` 只在候选筛选时用插值,最终求解始终用完整星历。两者共用的站心方程、掩带边界与升落曲线不变。
抽检不是全时段严格误差证明;两个分支的几何目标相同,但不保证采样点或 GeoJSON 字节完全相同。此选项不影响仅查询事件、指定站点接触或独立单时刻月影接口,也不影响日月食。
两个分支的全球起止、掩甚标记和中心线宽度均保留全项星历计算。绘图时应传入完整返回路径,包括可见性轮廓;丢弃该轮廓会调用瞬时足迹回退逻辑,其边界不能替代完整解析可见集。
路径中的 `BandContours` 是静态掩带的接触包络,`VisibilityContours` 是月亮处于地平线以上时的可见时间包络;两者与 `Footprints` 的瞬时采样分别承担静态边界、可见性边界和时间轴细节,不应互相替代。
### 掩甚时刻等时线
`OccultationPathOptions.GreatestTimeValues` / `GreatestTimeStep` 请求**掩甚时刻等时线**。与日食不同,`GreatestTimeValues []float64` 给的是力学时儒略日,最多保留 64 条(先去掉重复的时刻取值,按时间先后排序,超出时保留最早的 64 条),掩可见窗口之外或没有可用支路的时刻取值不会出现在结果里;它为空时改用 `GreatestTimeStep`,同样只在为正值时生效,且对齐到 UTC 整刻度。
结果写入 `StarOccultationPath.GreatestTimeContours`(行星路径是同名字段),元素类型 `OccultationGreatestTimeContour` 的 `JDE`、`Time`、`Segments` 与日食同义:`Time` 在按步长生成时是原始对齐时刻,显式给出的时刻取值则由 `JDE` 换算并抹到毫秒,两者都落在 UTC 时区,而支路点的时刻仍按路径时区;日食公共层的 `Time` 则直接落在输入时区。
边界口径同样一致:只出现在目标盘面与月面确有重叠且月亮在几何地平以上(不含蒙气差与半径修正)的地方,两端止于地平线或掩可见域边界,纬度 ±88° 以上不再延拓,同一时刻可能有多条互不相连的支路;不请求时既有输出不变。
```go
options := moon.OccultationPathOptions{
Algorithm: moon.OccultationPathAlgorithmExact, // 最终求解使用全项星历
DisableFootprints: true,
}
```
## 行星月掩
行星目标使用 `OccultationMercury` 到 `OccultationNeptune` 常量。下面以 `2025-02-01` 月掩土星为例,在靠近全球几何掩甚点的位置求 C1-C4:
```go
package main
import (
"fmt"
"time"
"b612.me/astro/moon"
)
func main() {
cst := time.FixedZone("CST", 8*3600)
start := time.Date(2025, 2, 1, 0, 0, 0, 0, cst)
events, err := moon.FindPlanetOccultations(
start, start.Add(24*time.Hour), moon.OccultationSaturn,
104.52219613, 55.25401991, 0,
moon.OccultationSearchOptions{},
)
if err != nil {
panic(err)
}
for _, event := range events {
fmt.Println(event.TargetID, event.Type, event.HasInternalContacts)
fmt.Println(event.ExternalImmersion.Format("2006-01-02 15:04:05.000 MST")) // C1
fmt.Println(event.InternalImmersion.Format("2006-01-02 15:04:05.000 MST")) // C2
fmt.Println(event.Greatest.Format("2006-01-02 15:04:05.000 MST"))
fmt.Println(event.InternalEmersion.Format("2006-01-02 15:04:05.000 MST")) // C3
fmt.Println(event.ExternalEmersion.Format("2006-01-02 15:04:05.000 MST")) // C4
}
}
```
输出结果:
```text
Saturn total true
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
```
`FindPlanetOccultationPaths` 的全球结果同时包含任意圆盘重叠的部分掩区域和整颗行星被遮住的全掩区域。`HasTotalBand` 表示是否存在全掩带,`GreatestTotalWidthKM` 是掩甚处全掩带宽;中心线、边界和启用时的瞬时足迹都带采样时刻。
## 月缘地形修正
默认月面是圆盘。固定地点的恒星、行星月掩搜索可以改用 `moon/limb` 子包的 ISAS/JAXA 月缘剖面(由 SELENE/Kaguya LALT 数据加工,说明见[日月食与凌日手册](eclipse.md#月缘地形修正)):掩始、掩终及行星的内外切按月心指向目标方向上的实际月缘半径精修,掩甚、食型和候选筛选仍按圆月面。
```go
import "b612.me/astro/moon/limb"
events, err := moon.FindStarOccultations(start, end, star, lon, lat, 0,
moon.OccultationSearchOptions{LunarLimb: limb.Default()})
// 或在上下文上统一设置
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{})
```
修正量通常为秒级,例如 2024-08-21 土星月掩各接触移动 `-1.8 s` 至 `-0.6 s`。
精度边界:
- 内置剖面按天平动 1° 格网插值,单个接触的月缘余量误差约 `±0.05″`(月面上约 100 m),由 16 像素/度的地形分辨率与插值共同决定。
- 掠掩时星迹贴着月缘走,余量只有百分之几角秒,遮挡段数与短暂的掩入、复现可能与实测不同;只保证主遮挡段的时刻与量级。例:2017-09-18 轩辕十四掠掩(希腊,IOTA 实测 5 段),本库给出 1 段,与实测最后一段的掩入、复现相差 `+0.6 s` / `-0.4 s`。
- 需要更细的掠掩剖面时,可用更高分辨率的地形自制剖面表,经 `limb.Parse` 读入或实现 `LunarLimb` 接口接入。
全球月掩路径(`FindStarOccultationPaths`、`FindPlanetOccultationPaths`,用 `OccultationPathOptions.LunarLimb` 或模块设置)只修正外接触南北界:
- 边界平移到月缘掠掩线:星迹在切点附近按直线处理,有效月缘取切点两侧 ±8° 位置角内的最高点。
- 月球在地平线附近的点保留圆月面。
- 行星全掩界、掩带填充、等时线与升落线仍按圆月面。
- 掠掩带内多次掩始掩终的逐点剖面不在这里计算。
- 2025-06-05 HR 4799 掠掩线最大移动约 `4 km`。
出图不需要单独开关:结果上的 `LunarLimbApplied` 记录月缘是否真正生效,`moon/svg` 按它在时标声明后写明口径。站心结果只有全部接触都按剖面收敛才置真,任一接触失败则整体保留圆月面;路径结果至少一个界线点被修正才置真。已收敛的接触不再落在圆月面上,站心图对这类结果跳过圆月面接触校验,因此内置或自定义剖面的事件都能直接出图。
## 月掩出图
`moon/svg` 分“搜索并渲染”和“渲染已计算结果”两组入口:
| 入口 | 用途 |
| --- | --- |
| `FindStarOccultationSVGs` / `FindPlanetOccultationSVGs` | 搜索窗口内全部月掩,逐事件渲染全球掩带图 |
| `StarOccultationPathSVG` / `PlanetOccultationPathSVG` | 渲染已有的全球路径(`Find...Paths` 的返回值) |
| `StarOccultationDetailedSVG` / `PlanetOccultationDetailedSVG` | 一页详细版式 |
| `FindLocalStarOccultationSVGs` / `FindLocalPlanetOccultationSVGs` | 指定地点的站心月面轨迹、白道与接触阶段图 |
| `LocalStarOccultationSVG` / `LocalPlanetOccultationSVG` | 渲染已有的固定地点事件 |
| `StarOccultationLimbProfileSVG` | 精密月掩剖面图:沿星迹的月缘起伏、全部遮挡区间与掩入/复现编号 |
`eclipse/svg` 的入口返回 `(string, bool)`,`moon/svg` 的入口返回 `(string, error)` 或 `([]string, error)`:月掩侧的第二个返回值是真正的错误(UT1 配非 UTC 时区、画布低于下限、路径非法),不是“画不出来”的标志。恒星按点光源处理,行星按有限圆盘处理:行星月掩的接触时刻由圆盘与月缘的几何求解,`OccultationPlanet` 决定圆盘半径;土星环既不参与接触计算、也不作为圆盘边界绘制。
掠掩(`HasTotalBand` 为假)可以整条没有中心线,此时全球图只画南北限。点源恒星按日月中心角距定食甚,有限盘面行星按外接触度量,分别与 `StarOccultationInfo.Greatest`、`PlanetOccultationInfo.Greatest` 同口径。
### 全球掩带图
`MapProjection` 提供与日食图一致的四档投影。投影只影响 SVG 表达,不改变底层 WGS84 地理结果:
| 选项 | 说明 |
| --- | --- |
| `MapProjectionAuto`(零值) | 按事件自动选择,高纬事件可能落到极图 |
| `MapProjectionEquirectangular` | 等经纬,跨反经线的长掩带最直观 |
| `MapProjectionNorthPolar` / `MapProjectionSouthPolar` | 极点居中的方位等距投影 |
| `MapProjectionOrthographic` | 正射球面,视点取事件中心,只画朝向视点的半球 |
正射球面版(`2025-06-05` 月掩 HR 4799):
```go
paths, err := moon.FindStarOccultationPaths(start, end, target,
moon.OccultationPathOptions{Step: 5 * time.Minute, TargetSpacingKM: 200})
if err != nil || len(paths) == 0 {
return
}
svg, err := moonsvg.StarOccultationPathSVG(paths[0], moonsvg.StarOccultationSVGOptions{
Width: 1200, Height: 800, Location: cst,
TimeLabelStep: 30 * time.Minute,
Projection: moonsvg.MapProjectionOrthographic,
})
if err != nil {
return
}
fmt.Println(len(svg))
```
![2025 月掩 HR 4799 正射全球掩带图](../img/lunar-occultation-hr4799-2025-06-05-global.svg)
同一路径改为南极投影,掩带落在高纬时比等经纬更清楚:
```go
south, err := moonsvg.StarOccultationPathSVG(paths[0], moonsvg.StarOccultationSVGOptions{
Width: 1200, Height: 800, Location: cst,
TimeLabelStep: 30 * time.Minute,
Projection: moonsvg.MapProjectionSouthPolar,
})
if err != nil {
return
}
```
![2025 月掩 HR 4799 南极区全球掩带图](../img/lunar-occultation-hr4799-2025-06-05-southpolar.svg)
把搜索与渲染合成一步时用 `Find...SVGs`,返回切片与命中事件一一对应:
```go
svgs, err := moonsvg.FindStarOccultationSVGs(start, end, target,
moon.OccultationPathOptions{Step: 5 * time.Minute, TargetSpacingKM: 200},
moonsvg.StarOccultationSVGOptions{Width: 1200, Height: 800, Location: cst})
fmt.Println(err, len(svgs))
```
`TimeLabelStep` 的零值为 30 分钟,负值关闭中心线上的时刻标记。单独的全球掩带地图最小 `640×480`,更小的画布返回 `ErrInvalidStarOccultationSVGOptions`(行星入口对应 `ErrInvalidPlanetOccultationSVGOptions`);图例、页脚与经纬刻度都按画布高度预留,画布越矮越容易压到页脚;本手册的南极示例用 `1200×800` 即可。
### 详细版式
详细版式把整场月掩合成一页 `1000×1414`:居中摘要、日月与目标天体的地心/站心数据块、一张**正射球面**的全球掩带图(南北限、可见/几何中心线、掩甚点、初掩/掩甚/终掩阶段点与 30 分钟时间标记),以及页脚说明。球面视点取事件中心,这一版式固定用正射球面,不接受其它投影;只需要单独的掩带地图时用上面的 `StarOccultationPathSVG` / `FindStarOccultationSVGs`。
```go
detailed, err := moonsvg.StarOccultationDetailedSVG(paths[0], target,
moonsvg.OccultationDetailedSVGOptions{Width: 1000, Height: 1414, Location: cst})
if err == nil {
_ = os.WriteFile("doc/img/lunar-occultation-hr4799-2025-06-05-detailed.svg", []byte(detailed), 0o644)
}
```
![2025 月掩 HR 4799 详细版式](../img/lunar-occultation-hr4799-2025-06-05-detailed.svg)
页内数据分为月亮地心坐标、目标天体、掩带路径点、接触时刻、历表与常数、天平动六块;横版画布把数据块排在地图右侧两栏三行,竖版把数据块排在球面下方三栏两行。球面掩带图使用 Natural Earth `1:50m` 海岸线,不含行政边界。
详细版按画布推导版式,实测最小画布 `670×595`(宽度下限 `480` 只是参数校验):地图与数据块放不下时返回 `ErrInvalidOccultationDetailedSVGOptions`(`800×600`、`1000×1414`、`1414×1000` 均可出图,`660×600`、`800×590`、`640×420`、`900×400` 会被拒绝)。
图上标注的“掩带宽”是掩甚处南北限的地面间距 `GreatestLimitSeparationKM`,不是中心线横向宽度 `Greatest.WidthKM`,两者不可互换。
掩甚时刻等时线要在路径层显式请求 `moon.OccultationPathOptions.GreatestTimeStep`:`moon/svg` 自身不提供开关,只绘制路径结果里已有的 `GreatestTimeContours`,因此请求必须在计算路径时提出,线的位置也由核心口径决定(`GreatestTimeStep` 对齐 UTC 整刻度;要按展示时区对齐,就先换算成力学时儒略日再传给 `GreatestTimeValues`)。
月掩的全球可见窗口通常只有数小时(HR 4799 样例为 4 小时 33 分),常用间隔比日食更密,为 15–30 分钟量级。使用 `DisableFootprints` 的紧凑掩带只在首次渲染时计算一次,之后同一路径复用缓存。
### 站心事件图
```go
localSVGs, err := moonsvg.FindLocalStarOccultationSVGs(
start, end, target,
121.56601, 6.80706, 0,
moon.OccultationSearchOptions{},
moonsvg.LocalStarOccultationSVGOptions{Width: 920, Height: 720, Location: cst},
)
fmt.Println(err, len(localSVGs))
```
本地图按指定观测者的站心几何绘制,下图沿用前文 `2025-06-05` 月掩进贤增九(HR 4799)的样例。该图的观测点为 `121.56601°E, 6.80706°N`,靠近全球几何掩甚点;图中的掩始、掩甚和掩终是该地点实际看到的站心接触时刻,并同时给出月面方向、白道、月高、方位和地平可见性。
![2025 月掩进贤增九指定地点见掩图](../img/lunar-occultation-hr4799-2025-06-05-local.svg)
### 精密月掩剖面图
上面的接触时刻都按圆月面求解。把月缘起伏逐点代入,可以得到星迹上每一刻的月缘几何和**全部**遮挡区间——深掩是掩入、复现两段,掠掩通常只有一段,月缘山谷还会把一次掩星切成多个分离区间。
```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})
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> 30585
```
![2025 月掩进贤增九精密月缘剖面图](../img/lunar-occultation-hr4799-2025-06-05-limb-profile.svg)
横轴是时刻,每格同时标出该时刻的月缘位置角;纵轴是相对参考圆月面的高度(km)。棕色实线是恒星方向的月缘半径,红色虚线是恒星中心到月心的角距,两者相交的区间就是遮挡区间,图上用阴影标出并给出编号;`Segments` 有几段就画几个面板,右侧另附天球平面缩略图与掩入/复现清单。同一次事件的圆月面结果(`11:14:03.127` / `12:50:12.789 UTC`)与剖面结果相差数秒,这个差值就是月缘起伏带来的修正。
约定:
- `info` 只取掩甚时刻与观测者,其余接触时刻由剖面重算并写入 `profile.Occultation`;它必须是 UTC 口径的结果,不能传 `...InUT1` 副本。
- `limb` 传 `nil` 时用模块或上下文的默认剖面(`WithLunarLimb`);两边都没有设置时返回 `ErrInvalidOccultationInput`。
- `Step` 零值为 200 ms,小于 10 ms 提升到 10 ms;每段至少 64 个采样、总数不超过 20000,越界时自动调整。**短于步长的遮挡或复现可能漏检**,要掠掩细节就把步长调小。
- `Segments` 是月缘起伏带内的采样段(深掩两段、掠掩通常一段),`Hidden` 是全部遮挡区间,`ReliefMinKM` / `ReliefMaxKM` 是判定所用的月缘半径范围,`ReferenceRadiusKM` 是 `MeanRadiusArcsec` 背后的参考月球半径。
- 剖面图默认 `920×620`,最小 `640×480`,更小返回 `ErrInvalidStarOccultationLimbProfileSVGOptions`;星迹全程落在起伏带外、没有任何采样时返回 `ErrEmptyStarOccultationLimbProfile`。
- 要在 UT1 口径下标时刻,用 `moon.StarOccultationLimbProfileInUT1(profile)` 换一份副本,或给绘图选项传 `TimeScale: astro.TimeScaleUT1`(此时 `Location` 必须是 UTC)。
### 时标口径与 UT1
月掩图默认按 UTC 口径出图并图内声明;`TimeScale: astro.TimeScaleUT1` 出 UT1 读数与 `DUT1 = UT1−UTC` 差值,此时 `Location` 必须是 UTC,配非 UTC 时区会直接返回错误而不是画错图。
GeoJSON 导出(`MarshalStarOccultation*` / `MarshalPlanetOccultation*` 的 `TimeMarkerOptions.TimeScale`)遵守同一契约:UT1 口径写 `time_scale` 成员、把全部 `time` 属性换成 UT1 读数,几何保持不变。完整约定见[时标声明](map-geojson.md#时标声明)。