Files
astro/planet/internal/semianalytic/planets/pln3.go
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

194 lines
3.8 KiB
Go

package planets
import (
"encoding/binary"
"errors"
"math/bits"
)
// bitReader 按位读取,字节内高位在前。
type bitReader struct {
b []byte
p int // 位位置
err error
}
// window 返回从当前位开始的 64 位窗口(高位对齐,越界补零),有效位至少 57 位。
func (br *bitReader) window() uint64 {
i := br.p >> 3
var w uint64
if i+8 <= len(br.b) {
w = binary.BigEndian.Uint64(br.b[i:])
} else {
for k := 0; k < 8; k++ {
w <<= 8
if i+k < len(br.b) {
w |= uint64(br.b[i+k])
}
}
}
return w << uint(br.p&7)
}
func (br *bitReader) skip(n int) {
br.p += n
if br.p > 8*len(br.b) {
br.err = errors.New("planets: truncated bit stream")
}
}
func (br *bitReader) get(n int) uint64 {
if n == 0 || br.err != nil {
return 0
}
if n > 56 {
hi := br.get(n - 32)
return hi<<32 | br.get(32)
}
v := br.window() >> uint(64-n)
br.skip(n)
return v
}
// gamma Elias-gamma,编码值为 n+1。
func (br *bitReader) gamma() int {
if br.err != nil {
return 0
}
z := bits.LeadingZeros64(br.window())
if z > 28 {
br.err = errors.New("planets: bad gamma code")
return 0
}
v := br.get(2*z + 1)
return int(v) - 1
}
func unzigzag(z int) int {
if z&1 == 0 {
return z >> 1
}
return -(z >> 1) - 1
}
// readElement3 读取 PLN3 元件位流:包络游程、幅角号(定长或同阶升序间隔)、成对共享位宽的量化系数。
func readElement3(r *reader, e *element, secDeg, nk int, sortedArgs bool) error {
if secDeg > maxDeg {
return errors.New("planets: secular degree too high")
}
n64 := r.uvar()
if n64 > uint64(nk) {
return errors.New("planets: too many terms")
}
nint := int(n64)
e.secular = make([]float64, secDeg+1)
for i := range e.secular {
e.secular[i] = r.f64()
}
step := r.f64()
size := r.uvar()
if r.err != nil {
return r.err
}
if size > uint64(len(r.b)-r.p) {
return errors.New("planets: truncated table")
}
br := &bitReader{b: r.take(int(size))}
if r.err != nil {
return r.err
}
env := make([]int, 0, nint)
for runs := br.gamma(); runs > 0 && br.err == nil; runs-- {
d := int(br.get(4))
cnt := br.gamma() + 1
if d > maxDeg || len(env)+cnt > nint {
return errors.New("planets: bad envelope runs")
}
for ; cnt > 0; cnt-- {
env = append(env, d)
}
}
if br.err != nil {
return br.err
}
if len(env) != nint {
return errors.New("planets: envelope count mismatch")
}
args := make([]int32, nint)
nbits := bits.Len(uint(nk - 1))
if nbits == 0 {
nbits = 1
}
prev, cur := -1, -1
for j := 0; j < nint; j++ {
a := 0
if sortedArgs {
if env[j] != cur {
prev, cur = -1, env[j]
}
a = prev + 1 + br.gamma()
prev = a
} else {
a = int(br.get(nbits))
}
if a >= nk {
return errors.New("planets: argument index out of range")
}
args[j] = int32(a)
}
top := 0
for _, d := range env {
if d > top {
top = d
}
}
// 按包络阶降序的稳定排序:pos[j] 为位流第 j 项的新位置
pos := make([]int, nint)
e.arg = make([]int32, nint)
k := 0
for d := top; d >= 0; d-- {
for j := 0; j < nint; j++ {
if env[j] == d {
pos[j], e.arg[k] = k, args[j]
k++
}
}
}
if nint == 0 {
return br.err
}
e.cnt = make([]int, top+1)
e.c, e.s = make([][]float64, top+1), make([][]float64, top+1)
for d := 0; d <= top; d++ {
for j := 0; j < nint; j++ {
if env[j] >= d {
e.cnt[d]++
}
}
e.c[d], e.s[d] = make([]float64, e.cnt[d]), make([]float64, e.cnt[d])
}
width := 0
coef := func() float64 {
neg := br.get(1) != 0
v := float64(br.get(width))
if neg {
v = -v
}
return v * step
}
for d := 0; d <= top; d++ {
for j := 0; j < nint; j++ {
if env[j] < d {
continue
}
width += unzigzag(br.gamma())
if width < 0 || width > 63 {
return errors.New("planets: bad coefficient width")
}
e.c[d][pos[j]] = coef()
e.s[d][pos[j]] = coef()
}
}
return br.err
}