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