Files
astro/star/catalog/parse.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

165 lines
4.4 KiB
Go

package catalog
import (
"fmt"
"math"
"strconv"
"strings"
"time"
"b612.me/astro/basic"
)
// GaiaDR3Columns 返回 Gaia DR3 归档导出的列映射 / column map for Gaia DR3 archive exports.
func GaiaDR3Columns() ColumnMap {
return ColumnMap{
ID: "source_id", RA: "ra", Dec: "dec", PMRA: "pmra", PMDec: "pmdec",
Parallax: "parallax", RadialVelocity: "radial_velocity", Magnitude: "phot_g_mean_mag",
BPRP: "bp_rp", Band: BandG, Epoch: julianYearTime(2016.0), Frame: basic.CoordinateFrameICRS,
IDPrefix: "Gaia DR3 ", PMRAIncludesCosDec: true,
}
}
// Hipparcos2007Columns 返回 VizieR 导出的 Hipparcos 2007 新归算 CSV 列映射,角度已换算为度;原始 hip2.dat 用 ReadHipparcos2007 /
// column map for VizieR CSV exports of Hipparcos 2007 in degrees; use ReadHipparcos2007 for raw hip2.dat.
func Hipparcos2007Columns() ColumnMap {
return ColumnMap{
ID: "HIP", RA: "RArad", Dec: "DErad", PMRA: "pmRA", PMDec: "pmDE",
Parallax: "Plx", Magnitude: "Hpmag", BV: "B-V", Band: BandHp,
Epoch: julianYearTime(1991.25), Frame: basic.CoordinateFrameICRS, IDPrefix: "HIP ",
PMRAIncludesCosDec: true, AnglesInRadians: false,
}
}
func julianYearTime(year float64) time.Time {
return basic.JD2DateByZone(2451545.0+(year-2000.0)*365.25, time.UTC, false)
}
func parseMissing(value string) bool {
switch strings.ToLower(strings.TrimSpace(value)) {
case "", "nan", "--", "null":
return true
default:
return false
}
}
func parseFloat(value string) (float64, bool, error) {
if parseMissing(value) {
return 0, false, nil
}
parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
if err != nil {
return 0, true, err
}
return parsed, true, nil
}
func parseInt(value string) (int64, bool, error) {
if parseMissing(value) {
return 0, false, nil
}
parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
if err != nil {
return 0, true, err
}
return parsed, true, nil
}
func parseUint(value string) (uint64, bool, error) {
if parseMissing(value) {
return 0, false, nil
}
parsed, err := strconv.ParseUint(strings.TrimSpace(value), 10, 64)
if err != nil {
return 0, true, err
}
return parsed, true, nil
}
func normalizedFrame(frame basic.CoordinateFrame) basic.CoordinateFrame {
if frame == "" {
return basic.CoordinateFrameICRS
}
return frame
}
func addEntry(c *Catalog, entry Entry, record int, options ReadOptions) error {
if entry.ID == "" {
return recordFailure(c, options, record, "missing ID")
}
if err := entry.Coordinate.Validate(); err != nil {
return recordFailure(c, options, record, err.Error())
}
if options.Filter != nil && !options.Filter(entry) {
return nil
}
c.add(entry)
return nil
}
func recordFailure(c *Catalog, options ReadOptions, record int, reason interface{}) error {
if options.Strict {
return fmt.Errorf("record %d: %v", record, reason)
}
c.skipped++
return nil
}
func parseCoordinate(id string, ra, dec, pmRA, pmDec, parallax, radial float64, epoch time.Time, frame basic.CoordinateFrame) basic.StarCoordinate {
if ra == 360 || ra == -0 {
ra = 0
} else {
ra = normalizeRA(ra)
}
// 负视差是测量噪声,按无视差处理;非有限值原样保留,交给 Validate 拒收。
if finite(parallax) && parallax < 0 {
parallax = 0
}
return basic.StarCoordinate{
ID: id,
RA: ra,
Dec: dec,
Epoch: epoch,
Frame: normalizedFrame(frame),
ProperMotionRACosDecMasPerYear: pmRA,
ProperMotionDecMasPerYear: pmDec,
ParallaxMas: parallax,
RadialVelocityKmPerSecond: radial,
}
}
func fixedFloat(line []byte, start, end int) (float64, bool, error) {
value, ok := fixedField(line, start, end)
if !ok {
return 0, false, nil
}
return parseFloat(value)
}
func fixedInt(line []byte, start, end int) (int64, bool, error) {
value, ok := fixedField(line, start, end)
if !ok {
return 0, false, nil
}
return parseInt(value)
}
func fixedField(line []byte, start, end int) (string, bool) {
if start < 0 || end < start || start >= len(line) || end > len(line) {
return "", false
}
return strings.TrimSpace(string(line[start:end])), true
}
func checkFinite(value float64) error {
if !finite(value) {
return fmt.Errorf("non-finite value %v", value)
}
return nil
}
func angularFromRadians(value float64) float64 {
return value * 180 / math.Pi
}