feat: 扩展天文计算能力
- 新增日食、月食、本地可见性、中心线、半影区域、SVG 图示与沙罗周期信息 - 新增行星冲合、留、方照、物理星历、视直径、相位、亮肢角、轨道节点等计算 - 新增木星伽利略卫星位置、现象与接触事件计算 - 新增恒星星表、星座判定、自行修正与观测辅助能力 - 新增 coord、formula、orbit、sundial、lite/sun、lite/moon 等扩展包 - 完善农历年号、月相英文别名、视差角、大气质量、折射、日晷与双星计算 - 增加 NASA、JPL Horizons、IMCCE 等回归测试数据与基线测试 - 重构基础算法文件组织,补充大量公开 API 注释和语义回归测试 - 更新中文和英文 README,补充示例、精度说明、SVG 配图
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// this file contains bright 9100 stars
|
||||
// 9100颗亮星列表
|
||||
|
||||
type InnerStarData struct {
|
||||
HR uint16 //Bright Star Number;[1/9110]+ Harvard Revised Number;亮星编号
|
||||
Name string //Name, generally Bayer(如天狼星:Alpha CMA) and/or Flamsteed(如天狼星:9 CMA) name
|
||||
HD uint32 //Henry Draper Catalog Number;HD星表编号
|
||||
Ra float64 //Ra J2000;J2000历元赤经
|
||||
Dec float64 //De J2000;J2000历元赤纬
|
||||
Mag float64 //视星等
|
||||
PmRA float64 //赤经年自行
|
||||
PmDec float64 //赤纬年自行
|
||||
RadVel float64 //径向速度 km/s
|
||||
RotVel float64 //自行速度 km/s
|
||||
Pc float64 //秒差距
|
||||
HIP uint32 //HIP星表编号
|
||||
}
|
||||
type StarData struct {
|
||||
InnerStarData
|
||||
ChineseName string
|
||||
ChineseAlias string
|
||||
ChineseBayerName string
|
||||
CommonName string
|
||||
CommonAliasName string
|
||||
Cst string
|
||||
CstChinese string
|
||||
}
|
||||
|
||||
func parseStarData(star []byte) (InnerStarData, error) {
|
||||
var err error
|
||||
var stardata InnerStarData
|
||||
if len(star) < 160 {
|
||||
return stardata, errors.New("invalid stardat")
|
||||
}
|
||||
for i := 0; i < 4; i++ {
|
||||
if star[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
stardata.HR = stardata.HR*10 + uint16(star[i]-48)
|
||||
}
|
||||
stardata.Name = string(bytes.TrimSpace(star[4:14]))
|
||||
for i := 25; i < 31; i++ {
|
||||
if star[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
stardata.HD = stardata.HD*10 + uint32(star[i]-48)
|
||||
}
|
||||
stardata.Ra, err = parseRa(star)
|
||||
if err != nil {
|
||||
return stardata, fmt.Errorf("parse ra failed:%v", err)
|
||||
}
|
||||
stardata.Dec, err = parseDec(star)
|
||||
if err != nil {
|
||||
return stardata, fmt.Errorf("parse dec failed:%v", err)
|
||||
}
|
||||
magOri := string(bytes.TrimSpace(star[102:107]))
|
||||
if magOri != "" {
|
||||
stardata.Mag, err = strconv.ParseFloat(magOri, 64)
|
||||
if err != nil {
|
||||
return stardata, fmt.Errorf("parse mag failed:%v", err)
|
||||
}
|
||||
} else {
|
||||
stardata.Mag = 9999.9999
|
||||
}
|
||||
stardata.PmRA, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[148:154])), 64)
|
||||
stardata.PmDec, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[154:160])), 64)
|
||||
if len(star) >= 170 {
|
||||
stardata.RadVel, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[166:170])), 64)
|
||||
}
|
||||
if len(star) >= 179 {
|
||||
stardata.RotVel, _ = strconv.ParseFloat(string(bytes.TrimSpace(star[176:179])), 64)
|
||||
}
|
||||
if len(star) > 161 {
|
||||
rc, _ := strconv.ParseFloat(string(bytes.TrimSpace(star[162:166])), 64)
|
||||
if rc != 0 {
|
||||
stardata.Pc = 1 / rc
|
||||
}
|
||||
}
|
||||
return stardata, nil
|
||||
}
|
||||
|
||||
func parseRa(star []byte) (float64, error) {
|
||||
var sec float64
|
||||
var err error
|
||||
ra := float64(0)
|
||||
for i := 75; i < 77; i++ {
|
||||
if star[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
ra = ra*10 + float64(star[i]-48)
|
||||
}
|
||||
minute := uint8(0)
|
||||
for i := 77; i < 79; i++ {
|
||||
if star[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
minute = minute*10 + (star[i] - 48)
|
||||
}
|
||||
ori := string(bytes.TrimSpace(star[79:83]))
|
||||
if ori != "" {
|
||||
sec, err = strconv.ParseFloat(ori, 64)
|
||||
if err != nil {
|
||||
return ra, err
|
||||
}
|
||||
}
|
||||
ra += float64(minute)/60 + sec/3600
|
||||
return ra * 15, nil
|
||||
}
|
||||
|
||||
func parseDec(star []byte) (float64, error) {
|
||||
var sec float64
|
||||
var err error
|
||||
underZero := false
|
||||
if star[83] == '-' {
|
||||
underZero = true
|
||||
}
|
||||
dec := float64(0)
|
||||
for i := 84; i < 86; i++ {
|
||||
if star[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
dec = dec*10 + float64(star[i]-48)
|
||||
}
|
||||
minute := uint8(0)
|
||||
for i := 86; i < 88; i++ {
|
||||
if star[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
minute = minute*10 + (star[i] - 48)
|
||||
}
|
||||
ori := string(bytes.TrimSpace(star[88:90]))
|
||||
if ori != "" {
|
||||
sec, err = strconv.ParseFloat(ori, 64)
|
||||
if err != nil {
|
||||
return dec, err
|
||||
}
|
||||
}
|
||||
dec += float64(minute)/60 + sec/3600
|
||||
if underZero {
|
||||
dec = -dec
|
||||
}
|
||||
return dec, nil
|
||||
}
|
||||
|
||||
var stardat [][]byte
|
||||
var starhdindex map[string]int
|
||||
var hr2detail map[uint16][]string
|
||||
var hr2hip map[uint16]uint32
|
||||
var chnidx map[string]uint16
|
||||
var parsedStarData []InnerStarData
|
||||
var cachedStarData []StarData
|
||||
var starDataOnce sync.Once
|
||||
var starDataErr error
|
||||
|
||||
func LoadStarData() error {
|
||||
starDataOnce.Do(func() {
|
||||
starDataErr = initStarData()
|
||||
})
|
||||
return starDataErr
|
||||
}
|
||||
|
||||
func initStarData() error {
|
||||
data := initStarCatalogData()
|
||||
stardat = bytes.Split(data, []byte("\n"))
|
||||
parsedStarData = make([]InnerStarData, len(stardat))
|
||||
cachedStarData = make([]StarData, len(stardat))
|
||||
for i, row := range stardat {
|
||||
parsed, err := parseStarData(row)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse star %d failed: %w", i+1, err)
|
||||
}
|
||||
parsedStarData[i] = parsed
|
||||
cachedStarData[i] = fullStarData(parsed)
|
||||
}
|
||||
chnidx = make(map[string]uint16, len(hr2detail)*2)
|
||||
for hr := 1; hr <= len(cachedStarData); hr++ {
|
||||
info, ok := hr2detail[uint16(hr)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
registerStarChineseIndex(uint16(hr), info[0])
|
||||
registerStarChineseIndex(uint16(hr), info[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerStarChineseIndex(hr uint16, name string) {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
chnidx[name] = hr
|
||||
if strings.HasSuffix(name, "星") {
|
||||
trimmed := strings.TrimSuffix(name, "星")
|
||||
if trimmed != "" {
|
||||
chnidx[trimmed] = hr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fullStarData(star InnerStarData) StarData {
|
||||
star.HIP = hr2hip[star.HR]
|
||||
if info, ok := hr2detail[star.HR]; ok {
|
||||
return StarData{
|
||||
InnerStarData: star,
|
||||
ChineseName: info[0],
|
||||
ChineseAlias: info[1],
|
||||
ChineseBayerName: info[2],
|
||||
CommonName: info[3],
|
||||
CommonAliasName: "",
|
||||
Cst: info[5],
|
||||
CstChinese: info[4],
|
||||
}
|
||||
}
|
||||
|
||||
return StarData{InnerStarData: star}
|
||||
}
|
||||
|
||||
func StarDataByChinese(name string) (StarData, error) {
|
||||
if err := LoadStarData(); err != nil {
|
||||
return StarData{}, err
|
||||
}
|
||||
if strings.HasSuffix(name, "星") {
|
||||
name = strings.TrimSuffix(name, "星")
|
||||
}
|
||||
hr, ok := chnidx[name]
|
||||
if !ok || hr == 0 || int(hr) > len(cachedStarData) {
|
||||
return StarData{}, errors.New("no such star")
|
||||
}
|
||||
return cachedStarData[hr-1], nil
|
||||
}
|
||||
|
||||
func StarDataByHR(hr int) (StarData, error) {
|
||||
if err := LoadStarData(); err != nil {
|
||||
return StarData{}, err
|
||||
}
|
||||
if hr <= 0 || hr > len(cachedStarData) {
|
||||
return StarData{}, errors.New("no such star")
|
||||
}
|
||||
return cachedStarData[hr-1], nil
|
||||
}
|
||||
|
||||
func (s InnerStarData) RaDecByJde(jde float64) (float64, float64) {
|
||||
//计算自行
|
||||
year := ((jde - 2451545.0) / 365.2422)
|
||||
return Precess(s.Ra+(year*s.PmRA/3600), s.Dec+(year*s.PmDec/3600), 2451545.0, jde)
|
||||
}
|
||||
|
||||
func (s StarData) RaDecByDate(date time.Time) (float64, float64) {
|
||||
jde := Date2JDE(date.UTC())
|
||||
return s.RaDecByJde(jde)
|
||||
}
|
||||
Reference in New Issue
Block a user