Files
astro/basic/star_catalog_data.go
T

225 lines
5.5 KiB
Go
Raw Normal View History

2026-05-01 22:38:44 +08:00
package basic
import (
"bytes"
"compress/gzip"
_ "embed"
"encoding/binary"
"errors"
2026-05-01 22:38:44 +08:00
"fmt"
"io/ioutil"
"math"
2026-05-01 22:38:44 +08:00
)
// starCatalogMagic star_catalog.dat 为 gzip 压缩、按列存储的 uvarint 整数格式。
const starCatalogMagic = "STRCAT02"
const starCatalogVersion = 2
const starCatalogDetailFields = 6
2026-05-01 22:38:44 +08:00
//go:embed star_catalog.dat
var starCatalogCompressed []byte
// supplementalStarDetails 载荷中无命名条目的星表元数据,字段依次为中文名、别名、拜耳命名、通用名、中文星座、IAU 星座。
var supplementalStarDetails = map[uint16][]string{
4799: {"进贤增九", "", "室女座25", "", "室女座", "Virgo"},
}
func initStarCatalogData() []InnerStarData {
2026-05-01 22:38:44 +08:00
reader, err := gzip.NewReader(bytes.NewReader(starCatalogCompressed))
if err != nil {
panic(err)
}
defer reader.Close()
payload, err := ioutil.ReadAll(reader)
2026-05-01 22:38:44 +08:00
if err != nil {
panic(err)
}
stars, detail, hip, err := decodeStarCatalogPayload(payload)
2026-05-01 22:38:44 +08:00
if err != nil {
panic(err)
}
for hr, record := range supplementalStarDetails {
detail[hr] = record
}
2026-05-01 22:38:44 +08:00
hr2detail = detail
hr2hip = hip
return stars
2026-05-01 22:38:44 +08:00
}
// starCatalogReader 顺序读取 uvarint 载荷。
type starCatalogReader struct {
data []byte
pos int
err error
}
2026-05-01 22:38:44 +08:00
func (r *starCatalogReader) uvarint() uint64 {
if r.err != nil {
return 0
2026-05-01 22:38:44 +08:00
}
value, n := binary.Uvarint(r.data[r.pos:])
if n <= 0 {
r.err = errors.New("truncated star catalog")
return 0
2026-05-01 22:38:44 +08:00
}
r.pos += n
return value
}
2026-05-01 22:38:44 +08:00
func (r *starCatalogReader) bytes(n uint64) []byte {
if r.err != nil {
return nil
2026-05-01 22:38:44 +08:00
}
if n > uint64(len(r.data)-r.pos) {
r.err = errors.New("truncated star catalog")
return nil
2026-05-01 22:38:44 +08:00
}
value := r.data[r.pos : r.pos+int(n)]
r.pos += int(n)
return value
}
// 解码公式须与编码端一致,运算顺序不可调换,否则结果不再逐位相同。
2026-05-01 22:38:44 +08:00
// starCatalogScaled 解码符号-幅值定点数,保留 -0。
func starCatalogScaled(v uint64, scale float64) float64 {
value := float64(v>>1) / scale
if v&1 != 0 {
return math.Copysign(value, -1)
2026-05-01 22:38:44 +08:00
}
return value
}
func starCatalogRa(v uint64) float64 {
ra := float64(v / 36000)
minute := (v / 600) % 60
sec := float64(v%600) / 10
ra += float64(minute)/60 + sec/3600
return ra * 15
}
func starCatalogDec(v uint64) float64 {
total := v >> 1
dec := float64(total / 3600)
dec += float64((total/60)%60)/60 + float64(total%60)/3600
if v&1 != 0 {
dec = -dec
2026-05-01 22:38:44 +08:00
}
return dec
}
2026-05-01 22:38:44 +08:00
func starCatalogMag(v uint64) float64 {
if v == 0 {
return 9999.9999
2026-05-01 22:38:44 +08:00
}
return starCatalogScaled(v-1, 100)
}
func starCatalogPc(v uint64) float64 {
if rc := starCatalogScaled(v, 1000); rc != 0 {
return 1 / rc
2026-05-01 22:38:44 +08:00
}
return 0
}
func starCatalogZigzag(v uint64) int64 {
return int64(v>>1) ^ -int64(v&1)
}
2026-05-01 22:38:44 +08:00
func decodeStarCatalogPayload(payload []byte) ([]InnerStarData, map[uint16][]string, map[uint16]uint32, error) {
if len(payload) < len(starCatalogMagic)+1 || string(payload[:len(starCatalogMagic)]) != starCatalogMagic {
return nil, nil, nil, errors.New("invalid star catalog magic")
}
if version := payload[len(starCatalogMagic)]; version != starCatalogVersion {
return nil, nil, nil, fmt.Errorf("unsupported star catalog version %d", version)
}
r := &starCatalogReader{data: payload, pos: len(starCatalogMagic) + 1}
count := r.uvarint()
if r.err == nil && count > math.MaxUint16 {
return nil, nil, nil, fmt.Errorf("star catalog count %d out of range", count)
}
stars := make([]InnerStarData, count)
for i := range stars {
stars[i].HR = uint16(i + 1)
stars[i].Name = string(r.bytes(r.uvarint()))
}
hd := int64(0)
for i := range stars {
hd += starCatalogZigzag(r.uvarint())
stars[i].HD = uint32(hd)
}
for i := range stars {
stars[i].Ra = starCatalogRa(r.uvarint())
}
for i := range stars {
stars[i].Dec = starCatalogDec(r.uvarint())
}
for i := range stars {
stars[i].Mag = starCatalogMag(r.uvarint())
}
for i := range stars {
stars[i].PmRA = starCatalogScaled(r.uvarint(), 1000)
}
for i := range stars {
stars[i].PmDec = starCatalogScaled(r.uvarint(), 1000)
}
for i := range stars {
stars[i].RadVel = starCatalogScaled(r.uvarint(), 1)
}
for i := range stars {
stars[i].RotVel = starCatalogScaled(r.uvarint(), 1)
}
for i := range stars {
stars[i].Pc = starCatalogPc(r.uvarint())
}
stringCount := r.uvarint()
if r.err == nil && stringCount > uint64(len(payload)) {
return nil, nil, nil, fmt.Errorf("star catalog string count %d out of range", stringCount)
}
stringsTable := make([]string, stringCount+1)
for i := 1; i < len(stringsTable); i++ {
stringsTable[i] = string(r.bytes(r.uvarint()))
}
indexes := make([][starCatalogDetailFields]uint64, count)
for k := 0; k < starCatalogDetailFields; k++ {
for i := range indexes {
indexes[i][k] = r.uvarint()
}
2026-05-01 22:38:44 +08:00
}
detail := make(map[uint16][]string)
for i, record := range indexes {
2026-05-01 22:38:44 +08:00
hasValue := false
values := make([]string, starCatalogDetailFields)
for k, index := range record {
if index >= uint64(len(stringsTable)) {
return nil, nil, nil, fmt.Errorf("detail string index out of range: hr=%d index=%d", i+1, index)
2026-05-01 22:38:44 +08:00
}
values[k] = stringsTable[index]
2026-05-01 22:38:44 +08:00
hasValue = hasValue || index != 0
}
if hasValue {
detail[uint16(i+1)] = values
2026-05-01 22:38:44 +08:00
}
}
hip := make(map[uint16]uint32)
prevHIP := int64(0)
for i := range stars {
value := r.uvarint()
if value == 0 {
continue
2026-05-01 22:38:44 +08:00
}
prevHIP += starCatalogZigzag(value - 1)
hip[uint16(i+1)] = uint32(prevHIP)
}
if r.err != nil {
return nil, nil, nil, r.err
}
if r.pos != len(payload) {
return nil, nil, nil, fmt.Errorf("%d trailing bytes in star catalog", len(payload)-r.pos)
2026-05-01 22:38:44 +08:00
}
return stars, detail, hip, nil
2026-05-01 22:38:44 +08:00
}